Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# SPDX-License-Identifier: Apache-2.0
HTTP_ADDR=:8080
# Branchy's own tables live in a `branchy` schema and reference a shared `core`
# schema (core.person/core.chat, upserted via core.touch). `docker compose` runs
# a bundled Postgres, seeds a minimal `core` schema (deploy/core-init.sql), and
# adds `options=-csearch_path%3Dbranchy` to this URL. Against a shared or
# standalone database, append `?options=-csearch_path%3Dbranchy` (in production
# this points at the shared core-postgres).
DATABASE_URL=postgres://branchy:branchy@localhost:5432/branchy?sslmode=disable
MIGRATIONS_DIR=migrations
AUTO_MIGRATE=true
Expand Down
11 changes: 10 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ This file is for coding agents working on Branchy. Keep the project minimal, sec
## Project Shape

- Branchy is one Go service.
- PostgreSQL is the only durable store.
- PostgreSQL is the only durable store. Branchy's tables live in a `branchy`
schema; Telegram identity/presence is delegated to a shared `core` schema
(`core.person`, `core.chat`) upserted via `core.touch`. In production this is
the shared `core-postgres`, reached on a single pool with `search_path=branchy`
(the retired standalone `branchy-postgres` is gone).
- Telegram uses long polling in the MVP.
- GitHub OAuth callbacks and repository webhooks are served by the same HTTP process.
- Notification delivery uses the durable PostgreSQL outbox, not direct sending inside the webhook handler.
Expand Down Expand Up @@ -59,6 +63,11 @@ Temporary Telegram/GitHub failures should retry with `retry_at` and `attempts`.

- Add new SQL migrations under `migrations/` with the next numeric prefix.
- Migrations must be safe to run once and tracked by `schema_migrations`.
- Migrations run against the `branchy` schema (`search_path=branchy`) and may FK
into `core.person`/`core.chat` or call `core.touch`; those live in the shared
`core-postgres` in production and are seeded locally by `deploy/core-init.sql`
so `docker-compose up` boots. Do not recreate the dropped local
`telegram_users`/`telegram_chats` tables.
- Do not edit old migrations after they may have been applied, unless the repo is still explicitly pre-release and the user asks for it.
- Keep `AUTO_MIGRATE=true` useful for local development.

Expand Down
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ Branchy runs startup migrations from `migrations/` and records completed
versions in `schema_migrations`. Keep `AUTO_MIGRATE=true` for local
development.

The bundled `docker compose` runs a local PostgreSQL and seeds a minimal shared
`core` schema (`deploy/core-init.sql`) so migrations that reference
`core.person` / `core.chat` boot cleanly. In the shared production deployment
Branchy instead connects to the existing `core-postgres`.

---

## Using Branchy
Expand All @@ -141,7 +146,13 @@ delivery is enabled, Branchy verifies that the Telegram user is a group

## How It Works

Branchy is one Go service with PostgreSQL as the only durable store.
Branchy is one Go service with PostgreSQL as its only durable store. Branchy's
own tables — subscriptions, the notification outbox, OAuth and runtime state —
live in a `branchy` schema. Telegram identity and presence (users and chats) are
delegated to a shared `core` schema (`core.person`, `core.chat`), which Branchy
upserts via `core.touch` before any dependent write. In production that schema
lives in the shared `core-postgres` database; local `docker compose` seeds a
minimal `core` schema so development boots the same way.

```text
telegram poller -> inline-button UI
Expand Down Expand Up @@ -184,7 +195,7 @@ Permanent delivery failures are marked `failed`.

| Variable | Required | Default | Description |
|:--|:--:|:--|:--|
| `DATABASE_URL` | yes | - | PostgreSQL connection string |
| `DATABASE_URL` | yes | - | PostgreSQL connection string; Branchy's tables live in the `branchy` schema, so a shared database needs `search_path=branchy` (append `options=-csearch_path%3Dbranchy`) |
| `PUBLIC_BASE_URL` | yes | - | Public HTTPS base URL |
| `TELEGRAM_BOT_TOKEN` | yes | - | Bot token from BotFather |
| `GITHUB_CLIENT_ID` | yes | - | GitHub OAuth App client ID |
Expand Down
21 changes: 17 additions & 4 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ Branchy is one Go service with three runtime surfaces:
- HTTP routes for GitHub OAuth and GitHub repository webhooks.
- A notification outbox worker that sends queued Telegram messages.

The service stores all durable state in PostgreSQL. GitHub webhook handlers do
not send Telegram messages directly; they create durable `notification_jobs`
rows and return quickly. The worker polls pending jobs with `FOR UPDATE SKIP
LOCKED`, sends Telegram messages, and updates job status.
The service stores its durable state in PostgreSQL. Branchy's own tables live in
a `branchy` schema (subscriptions, repositories, hooks, the notification outbox,
OAuth and runtime state), and Telegram identity/presence is delegated to a shared
`core` schema (`core.person`, `core.chat`) that Branchy upserts through the
`SECURITY DEFINER` `core.touch` function before any dependent write. In production
this is the shared `core-postgres` database; local `docker compose` seeds a
minimal `core` schema (`deploy/core-init.sql`) so migrations boot. GitHub webhook
handlers do not send Telegram messages directly; they create durable
`notification_jobs` rows and return quickly. The worker polls pending jobs with
`FOR UPDATE SKIP LOCKED`, sends Telegram messages, and updates job status.

## Packages

Expand Down Expand Up @@ -49,6 +55,13 @@ LOCKED`, sends Telegram messages, and updates job status.
webhook routes.
- One service keeps the codebase small. The durable outbox lives in PostgreSQL
instead of adding separate queue infrastructure.
- Branchy owns the `branchy` schema and, in production, connects to the shared
`core-postgres` as a dedicated least-privilege role (`branchy_core`) with
`search_path=branchy` on a single pool. Domain tables reference shared identity
in the `core` schema by natural key (Telegram id) and call `core.touch`
schema-qualified on that same pool — there is no separate `core` connection.
The old standalone `branchy-postgres` container is retired (its volume kept for
rollback).
- GitHub webhooks are owned per repository, not per subscription. Active
subscription events are unioned into the repository hook configuration. When
the active event union becomes empty, Branchy deletes its matching hook.
Expand Down
Loading