Repository navigation
How the Hub works
The Hub is one application wearing three faces. A public website explains what YOUNGO is and publishes its working groups, conferences and resources. A member interface behind sign-in carries the constituency's daily work: group workspaces, events, opportunities, decisions, elections and membership housekeeping. And a set of API endpoints carries the staff and review workflows that never appear on the public site. All of it runs inside a single Next.js process backed by a single PostgreSQL database. The member interface is a set of App Router client routes under (site)/(member) — real URLs rendered by the components in src/member/, not an embedded single-page app.
The deliberate surprise is what is missing. There is no admin panel. Payload's generated admin UI is disabled, and no equivalent was built to replace it. Staff administration — verifying an account, reviewing a membership appeal, publishing a decision — happens through API endpoints protected by the same permission rules as everything else, or through terminal scripts that call the Payload local API directly. This is a security decision as much as an aesthetic one: every admin surface that exists must be defended, and a generic CRUD panel that can edit any row in any collection is exactly the surface you do not want reachable by accident.
flowchart TB
subgraph Clients
Pub[Public visitors]
Mem[Members]
Staff[Staff & reviewers]
Agents[MCP agents]
end
subgraph App["Next.js application (one process)"]
Web[Public website & member SPA]
API["REST endpoints<br/>/api/*"]
Payload[Payload CMS<br/>local API]
Access["Access engine<br/>src/lib/access.ts"]
end
subgraph Data["PostgreSQL"]
Auth["authority_records"]
Rest[Collections:<br/>accounts, decisions,<br/>content, events…]
end
MCP["mcp-content server<br/>(separate process)"]
Mail["Email / push<br/>delivery"]
Pub --> Web
Mem --> Web
Staff --> API
Web --> API
Agents --> MCP --> API
API --> Access --> Payload
Payload --> Data
API --> Mail
Two arrows in that diagram deserve attention. The first is that every write — from a browser, a staff script, or an agent — funnels through the same access engine. There is no privileged path that skips src/lib/access.ts. The second is that mcp-content, the HTTP service that lets LLM agents create and review content, is a separate process that talks to the Hub over its public HTTP API. It holds no database credentials of its own; it cannot see anything an ordinary authenticated client could not.
Earlier iterations of the Hub kept two overlapping stores for "who is allowed to do what": a legacy participation ledger and a newer mandates table, with a layer of supersession logic keeping them coherent. That duplication has been retired. A single table, authority_records, now records every scoped responsibility in the constituency — working-group contact points, Council seats, body liaisons, team memberships, safeguarding officers — as a row with a role, a scope, a time window and provenance.
This matters because authority in YOUNGO is never ambient. A working-group contact point can manage their own group's events and records, but nothing in that mandate spills over to a neighbouring group. Modelling each responsibility as a row rather than a flag on the account is what makes that scoping enforceable — Organisation and permissions covers the model in full.
flowchart LR
subgraph Record["authority_records row"]
Role[role<br/>wg.contact_point]
Scope["scope<br/>working_group:47"]
Window["starts_at → ends_at"]
Prov[provenance<br/>kind, migrated_from]
end
Record --> Derive["deriveAuthority()<br/>resolves role → capabilities"]
Derive --> Cap["Capabilities used by<br/>endpoints & hooks"]
Every protected action travels the same route, whether it originates in a browser fetch, an MCP tool call or a script. The browser sends the request with the member's session; the endpoint resolves the account, derives their access profile from authority_records, and only then touches the data.
sequenceDiagram
actor Member
participant Browser as Member interface
participant Server as Hub server
participant Access as Access engine
participant DB as PostgreSQL
Member->>Browser: Open a group / submit a proposal
Browser->>Server: Request + session cookie
Server->>Access: Resolve account, derive profile
Access->>DB: Read authority_records
DB-->>Access: Scoped, time-bounded records
Access-->>Server: Capabilities for this account
alt Allowed
Server->>DB: Read or write permitted rows
DB-->>Server: Result
Server-->>Browser: Permitted result
else Not allowed
Server-->>Browser: Sign-in or access error
end
Interface-level hiding is never the security boundary. A hidden button is a courtesy; the same rule is enforced again server-side on every request. When you review a pull request that adds a member-facing feature, this is the question to ask: does the endpoint check, or only the component?
The permission vocabulary is not invented by the code — it encodes YOUNGO's actual governing documents. The role registry in src/lib/authority.ts cites its sources: the S13 Governance Policy for bodies and Council seats, the S25 mandate for working-group contact points, S17 for membership, S10 for elections, S23 and S04 for safeguarding. When the policy says a Council seat belongs to a working group rather than a person, the data model follows: seats resolve to scopes, and substitutes cover rather than duplicate.
The same discipline applies to data. The repository contains schema and code, never member data — there are no seed files full of invented names, no hard-coded officers, no fixture rows committed to source. A fresh deployment is empty until a provisioning script or an operator fills it, and demo environments get their casts generated into the database, not into git. Data and migrations explains why that matters and how migrations carry structure without carrying rows.
Beyond the core loop above, the Hub carries several self-contained domains, each with its own page here:
- Governance — decision pipelines from draft to outcome, Council electorates, selections, and credential-based elections. See Governance and decisions.
- Content — a draft → review → publish pipeline for pages, resources, announcements and opportunities, used identically by members and agents. See Content and publishing.
- Negotiations — track workspaces, document versioning, extraction review and contribution projects for UNFCCC negotiation work.
-
Intelligence — read-only query adapters over Hub data with strict evidence rules, plus a narrow, human-approved writeback for research notes. Its invariants are documented in
AGENTS.mdand enforced insrc/lib/access.ts. - Notifications — an outbox model that drains email and web-push through a cron endpoint, with categories members can opt out of individually. See Notifications and delivery.
Each of these is a consumer of the authority model, not a parallel system — a contact point's ability to publish to their group's page is the same authority_records row that lets them manage its events. That is the unifying idea of the Hub: one store of truth for who may do what, and every feature deriving from it rather than inventing its own answer.