Social SoftwareTechnical Reference
This page covers the technical detail behind the Social Software ecosystem as implemented in the current codebase: database schema, API surface, business logic modules, MCP server, federation protocol, and the reasoning behind key architectural decisions. For the product thinking and structural vision, start with the narrative.
Tech Stack
Application
| Technology | Purpose |
|---|---|
| Next.js 14 | Full-stack framework with API routes and server components |
| React 18 | Component model for all frontend pages |
| TypeScript 5.3 | Type safety across client and server, mirroring the SQL schema |
| Tailwind CSS 3.4 | Utility-first styling |
| Zod 3 | Runtime input validation on API routes |
Infrastructure
| Technology | Purpose |
|---|---|
| Supabase (PostgreSQL + ltree + RLS) | Database, auth, Row-Level Security, RPCs, real-time |
| Sandpack (CodeSandbox) | Lightweight React-only sandbox for fork execution |
| Anthropic Claude API | Schema-aware code generation and modification in the fork IDE |
| Stripe Connect | Automated creator payouts (code-complete, untested against live keys) |
| MCP SDK | Model Context Protocol server for IDE integration (Cursor, Claude Desktop, Windsurf) |
Database Schema
The database is the source of truth. Every table, RLS policy, and RPC function was defined before any application code was written.
Users and identity
| Table | Purpose |
|---|---|
profiles | Users: DID (decentralised identity), Stripe account, stake balance, voting power |
sessions | Telemetry: user engagement, device fingerprint for Sybil resistance |
An auto-profile trigger fires on user signup. Row-Level Security policies gate all access.
Four-tier layer tables
| Table | Purpose |
|---|---|
connector_layers | Tier 2: thin adapters to external data sources, with source_type, connection_template, adapter code, SHA-256 code_hash, and ltree lineage |
business_api_layers | Tier 3: orchestration and business logic with OpenAPI spec, connector_ids array, and ltree lineage |
apps | Tier 4: forkable UI layers with code, lineage_path as ltree, SHA-256 code_hash, declared scopes, optional business_api_id. Core platform pages are marked with is_core_component and core_route. |
app_manifests | Compositions: connector + business_api + ui_layer, with forked_from_id for manifest forking. The “app store” unit. |
app_embeds | Tracks which apps embed which, so revenue and attribution flow across composition boundaries |
Revenue and economics
| Table | Purpose |
|---|---|
revenue_pool | Money entering the ecosystem: tips, API fees, bounties, sponsorships |
distributions | Revenue splits to each ancestor in a UI layer lineage: generation, amount, settlement status |
manifest_distributions | Cross-tier revenue splits for composed apps: 40% UI, 35% Business API, 25% Connector |
bounties | Job board: event-sourced with optimistic concurrency via version |
Governance and moderation
| Table | Purpose |
|---|---|
schema_proposals | Proposed connector layer changes with proposer, SQL, and vote tallies |
votes | Stake-weighted voting on proposals |
flags | Community moderation: five unique flags auto-removes an app from discovery |
events | Immutable append-only audit trail: every mutation recorded as aggregate_type, aggregate_id, event_type, payload |
Federation and self-hosting
| Table | Purpose |
|---|---|
federation_nodes | Peer registry: name, URL, public key, status, last seen |
federation_imports | Log of layers imported from remote nodes, with hash verification |
protocol_tax | 1% protocol tax ledger for federated node revenue, settled via Stripe Connect |
shell_config | Maps routes to which fork of each core page is “live” |
Infrastructure
| Table | Purpose |
|---|---|
asset_refs | Blob storage registry: content hash + storage URI |
mcp_access_log | Audit log of MCP tool invocations: client, tool called, layer type, actor |
Database functions and views
| Function / View | Purpose |
|---|---|
get_ancestors(app_id) | Walks ltree upwards for UI layers, returns root → current (depth ASC) |
get_descendants(app_id) | Walks ltree downwards for UI layers, returns all forks |
get_connector_ancestors(id) | Walks ltree upwards for Tier 2 connector layers |
get_business_api_ancestors(id) | Walks ltree upwards for Tier 3 business API layers |
increment_balance() | Atomic balance update during revenue settlement |
increment_tip_total() | Atomic tip accumulator per app |
increment_session_count() | Atomic session counter for telemetry |
refresh_api_usage_weights() | Recalculates voting power: (last 30 days sessions) + (distributions received × 10) |
settle_manifest_revenue() | Cross-tier revenue settlement: 40% UI, 35% Business API, 25% Connector. Each tier's share cascades through its own lineage. |
resolve_shell_route() | Returns the app row for the currently configured fork of a given shell route |
get_embed_count(app_id) | Count how many apps embed a given app |
creator_stats (view) | Dashboard data aggregation per creator |
layer_ecosystem_stats (view) | Aggregate stats per tier: total layers, forks, sessions, max depth |
RLS: embeds are publicly readable; only authenticated users can create embed records.
Business Logic
Seven library modules in src/lib/ implement the core business logic. All are pure functions or thin wrappers around Supabase RPCs, with no UI dependencies.
Lineage (lineage.ts)
Encodes the provenance DAG using PostgreSQL's ltree extension. Every app's full ancestry is stored in a single column.
| Function | Purpose |
|---|---|
buildRootPath(appId) | Creates root path: app_{uuidNoDashes} |
buildForkPath(parentPath, forkId) | Extends path: {parentPath}.fork_{uuidNoDashes} |
getAncestors(appId) | Calls get_ancestors RPC, returns root → current |
getDescendants(appId) | Calls get_descendants RPC, returns full subtree |
buildTree(nodes) | Transforms flat list into nested tree structure for rendering |
generationLabel(n) | Human-readable: maintainer, parent, grandparent, great-grandparent |
Ancestor queries use the @> operator (ancestor contains descendant). Descendant queries use <@ (descendant is contained in ancestor). Both are single indexed queries, not recursive CTEs.
Revenue (revenue.ts)
Implements logarithmic decay distribution and settlement.
calculateSplit(amount, ancestors) — takes a total amount and the ancestor chain (root → current). Reverses the chain so index 0 = current maintainer, then applies 50% decay at each generation. The last ancestor receives whatever remains. Amounts below $0.000001 are dropped.
settleRevenue(appId, amount, source) — the full settlement pipeline:
- Record revenue entry in
revenue_pool - Walk ancestors using
getAncestors()RPC - Calculate split via
calculateSplit() - Insert into
distributionstable, callingincrement_balance()RPC for each recipient - Mark pool as settled, increment app tip total
- Append immutable event to audit trail
triggerStripePayouts(distributionIds) — Stripe Connect transfer for each unsettled distribution. Skips creators without a connected Stripe account and transfers below Stripe's $0.50 minimum. Code-complete but untested against live keys.
Linter (linter.ts)
The AI Verification Engine. Eight regex-based rules, each returning a severity (error or warning) and a human-readable message.
| Rule | Severity | What it catches |
|---|---|---|
no-external-fetch | error | fetch("https://...") — only relative /api/* allowed |
no-eval | error | eval() |
no-function-constructor | error | new Function() |
no-infinite-loop | error | while(true), for(;;) |
no-cookie-access | error | document.cookie |
no-location-redirect | error | window.location = ... |
no-direct-storage | warning | localStorage/sessionStorage — prefers api-client hooks |
out-of-scope-api | error | Detects HTTP method in fetch options. A GET /api/bounties requires api/bounties:read. A POST /api/bounties requires api/bounties:write. A call to /api/apps/:id/embed requires api/apps:embed. Write scope implies read; embed scope implies read for apps. |
Any error-severity issue blocks compilation. Warnings are informational. The linter runs on every keystroke in the fork IDE and on every MCP publish_layer call.
Layers (layers.ts)
CRUD operations for the four-tier architecture.
| Function | Purpose |
|---|---|
listLayers(type, limit) | Lists active layers by type, sorted by fork count |
getLayer(type, id) | Single layer with profile join |
getLayerByHash(type, hash) | Content-addressable lookup via SHA-256 hash |
forkLayer(type, parentId, ...) | Core fork operation: computes hash, extends ltree, increments parent fork count, records audit event |
createManifest(ownerId, ...) | Composes data + API + UI layer IDs into an app manifest |
listManifests(limit) | Published manifests with all tier layers expanded |
Federation (federation.ts)
Peer-to-peer layer sharing between independent ecosystem nodes.
| Function | Purpose |
|---|---|
getNodeInfo() | This node's public identity: name, URL, layer counts, capabilities |
getPublicLayers(type?, limit) | Layers available for federation, across all three types |
importRemoteLayer(url, id, type) | Fetches from remote, inserts as local root (depth 0), records audit event |
announceNode(name, url) | Upserts peer node in the registry |
getKnownNodes() | Lists all registered federation peers with status and last-seen timestamps |
Imported layers become local roots with their own lineage. They can be forked, built upon, and generate revenue within the importing node.
Seed (seed.ts)
Contains the genesis bounty board React code and the injected api-client.jsx that gates all network access through a controlled interface. The api-client also exports composition primitives: useEcosystemApp(appId) for fetching another app's code and metadata, and EcosystemEmbed for rendering another app inline in an isolated iframe. Used by the npm run seed script.
API Surface
18 API routes, each a thin orchestration layer: validate input, call business logic, return response.
Core app routes
| Route | Methods | Purpose |
|---|---|---|
/api/apps | GET, POST | Discovery feed (sorted by session_count) and genesis app publishing |
/api/apps/[id] | GET | Full app detail with code, scopes, and lineage path |
/api/apps/[id]/fork | POST | Fork controller: lint → hash → extend ltree → increment parent fork count → record event |
/api/apps/[id]/export | GET | Eject button: exports as a standalone Next.js project |
/api/apps/[id]/embed | GET | Serve app code + metadata for cross-app embedding. Used by the EcosystemEmbed component. |
Bounty routes
| Route | Methods | Purpose |
|---|---|---|
/api/bounties | GET | Query by status (open, in_progress, completed) |
/api/bounties | POST | Create bounty with event sourcing |
/api/bounties | PATCH | Claim or update: optimistic concurrency via version, claimer_id only set on claim transition |
Revenue and lineage
| Route | Methods | Purpose |
|---|---|---|
/api/tip | POST | Revenue settlement: validates amount with Number.isFinite(), walks lineage, applies decay cascade |
/api/lineage/[id] | GET | Full descendant tree with per-node revenue totals |
/api/revenue | GET | Earnings query by app or user |
AI, telemetry, governance
| Route | Methods | Purpose |
|---|---|---|
/api/ai-fork | POST | Claude API integration: validates auth token via supabaseAdmin().auth.getUser(), schema-aware code modification |
/api/telemetry | POST, PATCH | Session start and heartbeat |
/api/governance | GET, POST | Schema proposals, stake-weighted voting, community flagging |
/api/events | GET | Immutable audit trail filtered by aggregate type and ID |
Layers, manifests, federation, MCP
| Route | Methods | Purpose |
|---|---|---|
/api/layers | GET | List by type (data, api, ui) |
/api/manifests | GET, POST | App manifest CRUD (compose layers into publishable apps) |
/api/manifests/[id] | GET, POST | Manifest detail with full layer code; POST to fork a manifest with optional layer overrides |
/api/mcp | GET, POST | MCP server: GET returns human-readable info, POST handles JSON-RPC |
/api/federation | GET, POST | Node discovery, layer exchange, peer announcement |
MCP Server
The MCP server exposes the ecosystem to AI coding tools via two transports:
- stdio —
npm run mcpfor Cursor, Claude Desktop, Windsurf - HTTP —
POST /api/mcpwith JSON-RPC body for web clients
Resources (context discovery)
| URI | Returns |
|---|---|
ecosystem://registry/connector-layers | All active connector layers |
ecosystem://registry/business-api-layers | All active business API layers |
ecosystem://registry/ui-layers | All active UI layers |
ecosystem://registry/manifests | Published app manifests |
ecosystem://layer/{type}/{id}/code | Code + metadata for a specific layer |
Tools (9 total)
| Tool | Purpose |
|---|---|
list_ecosystem_layers | List active layers by type, with optional limit |
get_layer | Get one layer with full code and metadata |
fork_layer | Create fork: compute hash, extend ltree, record event |
publish_layer | Publish new layer (alias for fork with description) |
test_composition | Spin up temporary sandbox to test UI + API compatibility |
create_app_manifest | Compose layers into a publishable app |
list_app_manifests | Published manifests with layer details |
get_app_code | Get full source code of a UI layer app, with embedding instructions |
fork_manifest | Fork a composed app as a unit, optionally overriding individual layers |
Both transports call the same business logic modules. There is no separate MCP backend.
Frontend
14 pages covering the full user journey from discovery through forking, composition, earning, and governance.
| Page | Purpose |
|---|---|
/ | Discovery feed: apps sorted by session_count (real usage, not vanity metrics) |
/login | Supabase auth with ?next= redirect support |
/apps/[id] | App viewer: Sandpack live preview + tip button + lineage tree inline + scopes + export |
/apps/[id]/fork | AI fork IDE: Sandpack editor + Claude chat + lint validation + publish |
/apps/[id]/events | Immutable event log for a specific app |
/apps/new | Genesis app creator (publish root of a new DAG) |
/bounties | Bounty board: list (filterable by status), create, claim |
/lineage/[id] | Lineage dashboard: SVG fork tree + revenue per node + cascade preview |
/dashboard | Creator earnings, published apps, voting power, pending payouts |
/governance | Schema proposals, stake-weighted voting, flagging |
/compositor | App store: layer matrix with published manifests, Run and Fork actions per manifest |
/compositor/[id] | Manifest runner: live sandbox preview + layer stack + revenue split + fork lineage |
/compositor/new | Assemble app from layer dropdowns (data + API + UI) |
/meta | Platform metadata: registered core pages, shell config, system health |
Architecture Decisions
Why ltree instead of Neo4j
The provenance DAG is the most important data structure in the system. The obvious choice would be a graph database. PostgreSQL's ltree was chosen instead for three reasons:
- Operational simplicity. One database instead of two. Supabase gives auth, RLS, RPCs, and real-time subscriptions on the same instance that stores lineage. Neo4j would mean a second managed service, a second connection pool, and cross-database coordination for revenue settlement.
- Colocated transactions. Revenue settlement needs to atomically query ancestors, insert distributions, update balances, and record an audit event. With ltree in Postgres, this is one transaction.
- Shallow DAG. Fork lineages are typically 3–10 levels deep. ltree handles this effortlessly. The system is not traversing social networks with millions of edges.
The trade-off: ltree represents tree paths, not arbitrary graphs. Multi-parent merges (app C forks both A and B) are not supported at the lineage level. Multi-parent composition happens at the manifest level instead.
Why Sandpack instead of WebContainers
- Attack surface. WebContainers run a full Node.js environment (
fs,net,child_process). Sandpack only runs React components with no filesystem, no network, and no server-side execution. - Startup time. Sandpack instantiates in under 2 seconds. WebContainers take 5–10 seconds.
- API client injection. A read-only
api-client.jsxis injected into every sandbox, gating all network access through a controlled interface. Combined with the linter blockingfetch()to external URLs, the sandbox is structurally locked down.
WebContainers are the upgrade path for full-stack forks once the sandbox model is proven.
Why a custom linter instead of ESLint
- Speed. Eight regex-based rules return in milliseconds. ESLint with a plugin architecture for eight rules would be overkill.
- Scope enforcement. The most important rule checks whether actual API calls match declared permission scopes, distinguishing read from write by HTTP method. This is a domain-specific check that does not map to any existing ESLint rule.
- Auditability. Eight rules, each a clearly documented pattern. Anyone reading the file understands the entire security model in 90 seconds.
The trade-off: regex-based linting is evadable. The mitigation is defence in depth — the linter catches accidental violations, while the Sandpack CSP enforces the network boundary structurally. A production system would add AST-level analysis.
Why event sourcing
Every state mutation appends an immutable row to the events table. No updates, no deletes.
- Governance disputes. If a creator claims unfair flagging or revenue miscalculation, the full history of every action is available.
- Replay capability. The event stream can reconstruct any past state.
- Audit compliance. For a platform handling real money through creator payouts, an immutable trail is a legal requirement.
Why content-addressable hashing
Every piece of code is SHA-256 hashed on publish. This enables:
- Federation integrity. Imported layers are verified against their hash.
- Deduplication. Identical changes from different developers are recognised as the same code.
- Immutable references. Manifests point to specific layer hashes. Updating a layer creates a new hash; existing manifests continue to reference the original.
Known Limitations
| Area | Status | Notes |
|---|---|---|
| TypeScript types | Manual | Supabase types not generated via supabase gen types. Types are manually defined in supabase.ts. |
| Revenue atomicity | Sequential | settleRevenue() does sequential inserts. A mid-loop failure would leave partial distributions. |
| Stripe payouts | Untested | triggerStripePayouts() compiles but has never run against real keys. |
| Sybil resistance | Minimal | fingerprint field exists but behavioural entropy scoring is not implemented. |
| Governance execution | Records only | Proposals and votes persist but do not trigger automated schema changes. |
| MCP transport | Simplified | HTTP endpoint creates a new server per request. Production needs persistent SSE/WebSocket. |
| AST blast-radius | Not built | Would require AST parsing of all active forks to simulate schema changes. |
| Blob storage | Schema only | asset_refs table exists. No IPFS/Arweave integration. |