An AI-assisted workflow automation project with deterministic workflow contracts, observable execution, and explicit external-side-effect semantics.
StackBridge executes a deliberately bounded v1 pipeline:
manual input or Gmail polling -> schema-driven AI processing -> Slack or Notion
It is an engineering portfolio project, not a hosted service. The repository emphasizes executable contracts, PostgreSQL-backed ownership and concurrency invariants, migration reproducibility, bounded recovery behavior, and honest handling of ambiguous provider writes.
The current Dashboard rendering a fully synthetic local demo dataset.
| Surface | Supported |
|---|---|
| Triggers | Manual API/UI execution; Gmail polling |
| AI tasks | Classify and summarize; extract fields; generate content |
| Models | gpt-4o-mini; gpt-4o |
| Actions | slack_post; notion_create_page |
| Integrations | Gmail OAuth for polling; Slack OAuth and Notion OAuth for actions |
| Workflow shape | Trigger -> AI step -> one action, including schema-driven branching |
Scheduled triggers, webhooks, Gmail-send actions, workflow rollback, run replay, and automatic DLQ retry are not implemented.
- Workflow YAML is parsed into a strict, versioned executable contract. Unsupported fields, providers, models, and trigger/action combinations fail closed.
- Integration IDs are checked against the workflow owner when definitions are created, updated, activated, and executed.
- Gmail message claims use PostgreSQL uniqueness to prevent duplicate run creation for the same supported idempotency key.
- Refresh JWTs carry a
jtibound to a persisted, single-use session. Rotation consumes the old session atomically before creating its replacement. - OAuth token values are encrypted with AES-256-GCM before database storage.
- AI responses are validated against the workflow output schema. Semantic failures may receive bounded corrective attempts, with known token usage accumulated across billable attempts.
- Slack and Notion writes use bounded, provider-specific retry rules. Ambiguous writes are reported as an unknown external outcome rather than blindly re-sent.
- Terminal non-dry-run failures create an operator-visible DLQ entry. Resolving an entry acknowledges it; it does not replay the run.
- Alembic migrations bootstrap an empty PostgreSQL database and include downgrade/destructive-operation guards exercised by tests.
flowchart LR
UI[React 19 / Vite 8] --> API[FastAPI REST API]
API --> AUTH[Auth and refresh sessions]
API --> WF[Workflow and version services]
API --> EXEC[Run executor]
API --> DLQ[DLQ and cost aggregation]
SCHED[Embedded APScheduler<br/>SQLite job store] --> GMAIL[Gmail polling]
GMAIL --> EXEC
AUTH --> PG[(PostgreSQL 15)]
WF --> PG
EXEC --> PG
DLQ --> PG
EXEC --> OPENAI[OpenAI API]
GMAIL --> GMAILAPI[Gmail API]
EXEC --> SLACK[Slack API]
EXEC --> NOTION[Notion API]
The supported topology is one backend process/instance with an embedded scheduler. PostgreSQL is authoritative application state; the SQLite job store persists scheduler jobs. The project does not implement distributed scheduler leadership or multi-node scheduler coordination.
An empty migration bootstrap creates 13 application tables plus PostgreSQL's alembic_version table.
This example uses synthetic UUIDs. Replace them with active integrations owned by the current user before saving or executing it.
name: Support email triage
description: Classify a new support email and post a summary to Slack
version: "1"
trigger:
type: polling
platform: gmail
integration_id: 11111111-1111-4111-8111-111111111111
interval_seconds: 300
filter: "label:inbox"
ai_step:
task_type: classify_and_summarize
model: gpt-4o-mini
output_schema:
priority:
type: string
enum: [low, medium, high, urgent]
summary:
type: string
action:
type: slack_post
integration_id: 22222222-2222-4222-8222-222222222222
config:
channel: C0123456789
template: "StackBridge alert [{priority}]: {summary}"The current workflow editor validating a supported synthetic Gmail polling → gpt-4o-mini → Slack definition.
The editor also rejects incomplete definitions before they are persisted:
Fail-closed contract validation in the current editor, using synthetic local input.
Workflow edits create new version rows. Historical YAML can be inspected and copied back into the editor, but there is no automatic rollback operation.
Gmail polling uses the Gmail History API cursor. If a cursor becomes stale, recovery scans an explicit time window with server-controlled page, candidate-message, and matched-message budgets. It is bounded recovery, not arbitrary mailbox reconstruction.
The Gmail idempotency invariant prevents duplicate workflow-run creation for the same Gmail message key under the supported polling flow. It does not imply exactly-once provider delivery.
Provider failures are classified as either:
- Definite failure: the provider rejected the request or a retryable response exhausted its bounded budget.
- Unknown external outcome: a transport failure, server response, or unreadable success response leaves open the possibility that the provider write completed.
Run Inspector rendering a synthetic local success with step statuses, recorded tokens, model, and estimated model cost.
Unknown outcomes require operator investigation. StackBridge does not automatically retry or replay them.
Run Inspector rendering a synthetic local demo run whose Notion write outcome is unknown; no external write was performed for this fixture.
Run and step cost fields are estimated model cost derived from recorded prompt/completion tokens and the price table for the supported model allowlist. They are not an invoice or authoritative provider-account spend. The cost summary reports all-time recorded tokens and estimated model cost, a per-workflow breakdown, and a recent 14-day series.
The current Cost Dashboard aggregating coherent synthetic local run data; values are estimates, not provider billing.
Manual execution can request dry-run mode. A dry run still executes the AI step and can incur real model usage. It resolves the selected action and builds a provider-specific preview without fetching a provider token or performing the Slack/Notion write.
Run Inspector rendering a schema-correct synthetic Slack preview with Dry Run: Yes; no provider write occurred.
- Argon2 password hashing.
- Signed JWT access tokens and persisted one-time refresh-session rotation.
- AES-256-GCM encryption before OAuth tokens are stored in PostgreSQL.
- Owner-scoped workflow, integration, run, cost-summary, and DLQ queries; foreign resources are hidden where the API exposes object lookup.
- OAuth state validation with a ten-minute, single-use in-process state record.
- Bounded AI/provider retry budgets and explicit unknown-outcome handling.
- Minimum read-only GitHub Actions permissions and no real provider credentials in CI.
See SECURITY.md for private vulnerability reporting guidance.
| Layer | Technology |
|---|---|
| Frontend | React 19, Vite 8, React Router 7, Recharts, JavaScript |
| Backend | Python 3.11.9, FastAPI, Pydantic v2, SQLAlchemy 2 async |
| Data | PostgreSQL 15, Alembic; SQLite APScheduler job store |
| AI | OpenAI Chat Completions over httpx |
| Auth | Argon2, python-jose, access/refresh JWTs, persisted refresh sessions |
| Providers | Gmail, Slack, and Notion OAuth/API integrations |
| CI | GitHub Actions on Python 3.11.9 and Node.js 24 |
- Git
- Python 3.11
- Node.js 24 with npm
- Docker with Compose
git clone https://github.com/BadrDyane/stackbridge.git
cd stackbridge/backend
docker compose up -d postgresIf your Docker installation exposes the standalone command, use docker-compose up -d postgres instead.
PowerShell:
py -3.11 -m venv venv
.\venv\Scripts\Activate.ps1
python -m pip install -r requirements-dev.txt
Copy-Item .env.example .envmacOS/Linux:
python3.11 -m venv venv
source venv/bin/activate
python -m pip install -r requirements-dev.txt
cp .env.example .envGenerate an application signing secret and a 64-character hexadecimal encryption key:
python -c "import secrets; print('APP_SECRET_KEY=' + secrets.token_urlsafe(48)); print('ENCRYPTION_KEY=' + secrets.token_hex(32))"Copy those two generated values into backend/.env. The committed example contains deliberately unusable secret placeholders.
The database, migrations, tests, frontend build, and core API startup do not require live provider calls. Actual OpenAI execution and Gmail/Slack/Notion OAuth or API operations require valid provider credentials in .env.
From backend/ with the virtual environment active:
alembic upgrade head
uvicorn app.main:app --reload --port 8000- Health: http://localhost:8000/health
- Swagger UI: http://localhost:8000/docs
- OpenAPI document: http://localhost:8000/openapi.json
The reload flag is for local development only.
In another terminal:
cd stackbridge/frontend
npm ci
npm run devOpen http://localhost:5173.
Backend (from backend/, with PostgreSQL running):
python -m pytest -c pytest.ini testsFrontend (from frontend/):
npm test
npm run lint
npm run buildSwagger/OpenAPI is the canonical complete reference. The implemented routes are:
| Method | Route | Purpose |
|---|---|---|
| GET | /health |
API/database health |
| POST | /auth/register |
Register an account |
| POST | /auth/login |
Issue an access/refresh token pair |
| POST | /auth/refresh |
Atomically rotate a refresh session |
| GET | /auth/me |
Read the current user |
| GET, POST | /workflows |
List or create workflows |
| GET | /workflows/{workflow_id} |
Read an owned workflow |
| GET, POST | /workflows/{workflow_id}/versions |
List or create workflow versions |
| POST | /workflows/validate |
Validate YAML without saving |
| PATCH | /workflows/{workflow_id}/trigger-config |
Upsert Gmail polling configuration |
| PATCH | /workflows/{workflow_id}/action-config |
Upsert the action override |
| POST | /workflows/{workflow_id}/activate |
Validate and activate a workflow |
| POST | /workflows/{workflow_id}/deactivate |
Deactivate a workflow |
| POST | /runs/trigger |
Execute a manual run, optionally dry-run |
| GET | /runs |
List owned runs |
| GET | /runs/cost-summary |
Read owner-scoped estimated model costs |
| GET | /runs/{run_id} |
Inspect an owned run and its steps |
| GET | /integrations/{platform}/auth-url |
Begin an authenticated OAuth flow |
| GET | /integrations/{platform}/callback |
Complete provider OAuth |
| GET | /integrations |
List active integrations |
| POST | /integrations/{integration_id}/test |
Test an owned integration |
| DELETE | /integrations/{integration_id} |
Disconnect an owned integration |
| GET | /dlq |
List unresolved owned DLQ entries |
| POST | /dlq/{dlq_id}/resolve |
Acknowledge an owned DLQ entry |
At the Phase 3F checkpoint, the repository passed:
- 167 PostgreSQL-backed backend tests on Python 3.11.9, including full migration bootstrap/downgrade verification.
- 26 frontend tests on Node.js 24, plus repository lint and a production build.
- A clean-clone exercise using a fresh Python environment,
npm ci, an empty PostgreSQL database upgraded to Alembic head, API startup, and a healthy database probe.
Both GitHub Actions workflows run the full relevant suites; neither workflow uses real provider secrets.
- The scheduler topology is one backend process/instance; there is no distributed leader election.
- Only manual and Gmail-polling triggers, and Slack/Notion actions, are executable v1 contracts.
- There is no replay engine or automatic DLQ retry.
- Unknown provider outcomes require operator investigation and may represent a completed external write.
- Stale Gmail cursor recovery is bounded and may leave a gap before its configured recovery window.
- Browser access and refresh tokens are stored in
localStorage. Refresh recovery is single-flight within one page, not coordinated across tabs. - Client logout clears browser state but does not proactively revoke the current server-side refresh session.
- OAuth state is stored in the backend process, so an in-progress OAuth flow does not survive a process restart or transfer to another instance.
stackbridge/
|-- .github/workflows/ # Backend migration and frontend verification
|-- backend/
| |-- alembic/ # Five migrations through the current head
| |-- app/
| | |-- api/ # Auth, workflow, run, integration, and DLQ routes
| | |-- contracts.py # Executable v1 allowlists and model pricing
| | |-- engine/ # Normalize -> AI -> action execution
| | |-- integrations/ # Gmail trigger; Slack/Notion actions; OAuth
| | |-- models/ # 13 application tables, including refresh sessions
| | |-- scheduler/ # Gmail polling and reconciliation
| | `-- services/ # Workflow, DLQ, integration, and LLM services
| `-- tests/ # PostgreSQL-backed backend verification
|-- frontend/
| |-- src/api/session.js # Token-pair persistence and legacy migration
| |-- src/utils/runOutcome.js # Safe external-outcome presentation
| `-- tests/ # Session/client and run-outcome tests
`-- screenshots/ # Sanitized, current public product evidence
Built by Badr Dyane.
StackBridge is licensed under the MIT License.






