Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
0a5c90f
feat(agents): stabilise multi-agent runtime + Langfuse tracing
TheAlexPG May 3, 2026
680883d
feat(agents): live WS broadcast, sub-agent tool-result rewrite, golde…
TheAlexPG May 3, 2026
8a94680
fix(agents/prompt): create_connection BEFORE place_on_diagram for con…
TheAlexPG May 3, 2026
e5fdc91
feat(agents): auto-pick connection handles + agent override
TheAlexPG May 3, 2026
1beaa71
fix(agents): max_steps=200 + planner-routing + inferred connections
TheAlexPG May 3, 2026
e7220f3
fix(agents): raise LLM call timeout 90s → 2000s
TheAlexPG May 3, 2026
71f74d0
fix(agents/prompts): copy plan's diagram_id verbatim, don't override …
TheAlexPG May 3, 2026
eacd358
fix(agents): hide raw user request from researcher / planner / diagram
TheAlexPG May 3, 2026
816106f
fix(agents): bump Findings/Explanation summary cap 4000 → 16000
TheAlexPG May 3, 2026
951a992
fix(agents): server-side dedup, explicit handles, conflict detection,…
TheAlexPG May 4, 2026
36782f9
fix(agents): child-diagram dedup + delete reviewer with mandatory reason
TheAlexPG May 4, 2026
a503dc1
fix(agents): cap LLM call timeout at 10 min (600s)
TheAlexPG May 4, 2026
02b0adc
fix(agents): destructive reviewer 400-loop + connection consolidation…
TheAlexPG May 4, 2026
2d920cd
fix(agents): json_schema response_format for reviewer + health-check,…
TheAlexPG May 4, 2026
29abb19
fix(agents/llm): route OpenRouter via OpenAI-compat regardless of mod…
TheAlexPG May 4, 2026
6269eb9
feat(agents): pull context window from OpenRouter /api/v1/models
TheAlexPG May 4, 2026
400b5d0
fix(agents): surface destructive-op `reason` requirement in tool defs
TheAlexPG May 4, 2026
a266d00
fix(agents): include field description in validation error hints
TheAlexPG May 4, 2026
21cf8b8
feat(agents): force-finalize on 4 consecutive identical tool calls
TheAlexPG May 4, 2026
4d6a97d
feat(chat): round send button + red cancel state with pulsing ring
TheAlexPG May 4, 2026
07c24d4
fix(agents): tool-loop detector counts repeats in window, not streak
TheAlexPG May 4, 2026
ce0f525
fix(agents): make destructive-op `reason` optional so deletes actuall…
TheAlexPG May 4, 2026
23b0eb2
revert(agents): keep destructive-op `reason` required (the safety hoo…
TheAlexPG May 4, 2026
d9a9a64
feat(agents): strip destructive-op safety layer — delete tools take i…
TheAlexPG May 4, 2026
5d34010
feat(tracing): nest sub-agents under supervisor + structured span I/O
TheAlexPG May 4, 2026
5080154
fix(tracing): collapse multi-visit supervisor into one span
TheAlexPG May 4, 2026
01f4276
fix(tracing): include sub-agent's actual report in span output
TheAlexPG May 4, 2026
6243e13
feat(tracing): stamp full message history on each span's metadata
TheAlexPG May 4, 2026
17bc16a
feat(chat): full markdown + activity animations on tool / node / thin…
TheAlexPG May 4, 2026
ab158ed
fix(chat): cancel actually stops, history loads, sessions get LLM titles
TheAlexPG May 4, 2026
86e6e0e
fix(chat): session history loader now uses content_text from wire
TheAlexPG May 4, 2026
15fe97c
style(chat): add motion primitives + markdown rhythm
TheAlexPG May 4, 2026
c57b09f
feat(chat): make in-flight tool card unmistakable
TheAlexPG May 4, 2026
1bad4f2
refactor(chat): consolidate node indicator motion to one heartbeat
TheAlexPG May 4, 2026
f200759
refactor(chat): align thinking pill with the new motion hierarchy
TheAlexPG May 4, 2026
0f1e43d
style(chat): tone down composer cancel ring
TheAlexPG May 4, 2026
5d6448c
fix(agents): per-tool commit, token aggregation, tool_call SSE pipe
TheAlexPG May 4, 2026
a29c2b0
feat(chat): tool icons popover, magic prompt starters, jwt refresh
TheAlexPG May 4, 2026
bf96ecd
fix(diagram): back button walks up the C4 hierarchy instead of dashboard
TheAlexPG May 4, 2026
2762807
docs(spec): github repo researcher design
TheAlexPG May 4, 2026
5686101
feat(workspaces): github token encrypted storage
TheAlexPG May 4, 2026
2488d60
feat(objects): repo_url and repo_branch columns
TheAlexPG May 4, 2026
f8ce9db
feat(api): github token + repo lookup endpoints
TheAlexPG May 4, 2026
4210419
feat(settings): workspace GitHub token UI block
TheAlexPG May 4, 2026
bdeb20c
feat(inspector): GitHub repo field on Container/System nodes
TheAlexPG May 4, 2026
e1c4171
feat(repo): http client shapes for github read tools
TheAlexPG May 4, 2026
59221b0
feat(repo-tools): 9 read-only github tools with per-turn lru cache
TheAlexPG May 4, 2026
ce4831e
feat(supervisor): per-turn repo manifest + state slot
TheAlexPG May 4, 2026
c257d96
feat(repo-agent): repo_researcher node with parameterized prompt
TheAlexPG May 4, 2026
d66548c
feat(supervisor): dynamic delegate_to_repo_<slug> tools + manifest block
TheAlexPG May 4, 2026
6dc24ef
feat(graph): wire repo_researcher into LangGraph topology
TheAlexPG May 4, 2026
5ef5b41
test(repo): tools, manifest, node, supervisor extension, graph routing
TheAlexPG May 4, 2026
660695d
feat(repo-manifest): recursive descendant discovery with depth cap
TheAlexPG May 4, 2026
8e160f5
feat(supervisor): cookbook examples for chatbot Q&A and visualize-thi…
TheAlexPG May 4, 2026
a1baa24
test(repo): multi-repo manifest + supervisor routing
TheAlexPG May 4, 2026
a25a394
refactor(supervisor): rename delegate_to_repo → delegate_to_git_resea…
TheAlexPG May 4, 2026
dbdddfd
feat(prompts): warn researcher off git questions, point at delegate_t…
TheAlexPG May 4, 2026
cc630fc
fix(agents): serialize db access during commits; fail-soft on FK viol…
TheAlexPG May 4, 2026
c97f180
fix(researcher): strip markdown fence; graceful Findings truncation
TheAlexPG May 4, 2026
cb5b49a
feat(repo-manifest): walk ancestors via scope_object_id, cap 3 levels
TheAlexPG May 5, 2026
5a9a29a
docs(spec): manifest now walks both directions with depth cap
TheAlexPG May 5, 2026
c4e8064
Merge branch 'feat/github-repo-researcher' into feat/ai-agents-invest…
TheAlexPG May 5, 2026
140c12d
fix(tracing): propagate chat session id to Langfuse session_id
TheAlexPG May 5, 2026
765ee87
fix(tracing): preserve chat session id across follow-up turns in same…
TheAlexPG May 5, 2026
c2bb19a
increase message limit
TheAlexPG May 5, 2026
539aed1
fix(chat): restore tool icons in resumed chat history
TheAlexPG May 5, 2026
71661fc
docs(env+readme): document agent platform env vars and surface AI fea…
TheAlexPG May 5, 2026
8715476
update gitignore
TheAlexPG May 5, 2026
66544dc
merge: resolve main into feat/ai-agents-investigation
TheAlexPG May 5, 2026
4ec32d3
fix(ci): unbreak build-backend, build-frontend, test on PR #14
TheAlexPG May 5, 2026
317105e
fix(migrations): notifications upgrade was a no-op
TheAlexPG May 5, 2026
1fe775f
test(collab-undo): skip flaky in-process race test
TheAlexPG May 5, 2026
1c5e86e
fix(evals): make fast suite pass after repo tools landed
TheAlexPG May 5, 2026
a3d66e7
fix(evals): switch Makefile to explicit cd .. for pytest
TheAlexPG May 5, 2026
32f72d2
fix(pytest): add pythonpath=['.'] so evals.lib resolves under fresh C…
TheAlexPG May 5, 2026
7a2ed00
fix(packaging): include evals/ in setuptools find so eval suite resolves
TheAlexPG May 5, 2026
1b82657
ci(test): export PYTHONPATH=backend for fast eval suite
TheAlexPG May 5, 2026
df7c959
ci(test): top-level conftest puts backend/ on sys.path
TheAlexPG May 5, 2026
878d371
ci(evals): mutate sys.path inside evals conftest itself
TheAlexPG May 5, 2026
293eff3
chore(gitignore): un-hide backend/evals/lib/
TheAlexPG May 5, 2026
31323a4
test(safety): auto-bootstrap an archflow_test DB so tests never touch…
TheAlexPG May 6, 2026
02392ce
fix(migrations): repair-notifications + flag AGENTS_SECRET_KEY as req…
TheAlexPG May 6, 2026
7541013
feat(agent-settings): rename edits policy + flip default to live
TheAlexPG May 6, 2026
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
34 changes: 34 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,37 @@ GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI=http://localhost:8000/api/v1/auth/oauth/google/callback
FRONTEND_URL=http://localhost:5173

# =============================================================================
# REQUIRED: Agent platform encryption key
# =============================================================================
# Symmetric Fernet key used to encrypt every workspace's LLM provider API key
# and GitHub PAT at rest. Without this:
# * Saving a workspace LLM key → 500 error → no agent can call an LLM.
# * Saving a GitHub PAT → 500 error → repo researcher can't read repos.
# * Any "agent settings" save returns "AGENTS_SECRET_KEY is not configured".
#
# Generate ONCE per deployment (32-byte url-safe base64, exactly 44 chars):
# python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
#
# DO NOT rotate after secrets are saved — there's no auto re-encryption.
# Losing this key locks every workspace's LLM/GitHub credentials forever.
# Treat it like JWT_SECRET: keep it in your secrets manager, back it up.
AGENTS_SECRET_KEY=

# Langfuse — optional admin-instance tracing for agent LLM calls.
# When all three are set, app/agents/tracing.py registers LiteLLM callbacks
# at startup and routes per-call telemetry. Per-call gating is governed by
# the workspace's analytics_consent (off / errors_only / full). Leave blank
# to disable tracing entirely.
LANGFUSE_PUBLIC_KEY=
LANGFUSE_SECRET_KEY=
LANGFUSE_HOST=

# Agent invocation rate limits — operator-level (not per-workspace). Defaults
# below are 10× the original spec. Override only if you need to throttle
# harder or relax further.
# AGENT_RATE_LIMIT_API_KEY_PER_HOUR=6000
# AGENT_RATE_LIMIT_API_KEY_PER_DAY=60000
# AGENT_RATE_LIMIT_USER_PER_DAY=10000
# AGENT_RATE_LIMIT_WORKSPACE_PER_DAY=100000
75 changes: 75 additions & 0 deletions .github/workflows/eval.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Agent Evals (slow, costed)

on:
workflow_dispatch:
inputs:
suite:
description: 'Suite to run (fast/slow/all/single-test)'
required: true
default: 'slow'
type: choice
options:
- fast
- slow
- all
- single-test
test_path:
description: 'For single-test: relative path like evals/test_planner.py::TestX::test_y'
required: false
default: ''
profile:
description: 'Threshold profile (lenient/strict)'
required: false
default: 'lenient'
type: choice
options:
- lenient
- strict

jobs:
eval:
runs-on: ubuntu-latest
environment: eval-llm-keys
timeout-minutes: 60
defaults:
run:
working-directory: backend

steps:
- uses: actions/checkout@v4

- uses: astral-sh/setup-uv@v3
with:
version: latest

- name: Set up Python
run: uv python install 3.12

- name: Install deps
run: uv sync --frozen --extra agents --extra dev --extra evals

- name: Run eval suite
env:
EVAL_MODEL: ${{ secrets.EVAL_MODEL }}
EVAL_LLM_KEY: ${{ secrets.EVAL_LLM_KEY }}
EVAL_LLM_BASE_URL: ${{ secrets.EVAL_LLM_BASE_URL }}
EVAL_THRESHOLD_PROFILE: ${{ inputs.profile }}
run: |
case "${{ inputs.suite }}" in
fast) make -C evals fast ;;
slow) make -C evals slow ;;
all) make -C evals fast slow ;;
single-test) uv run --extra agents --extra dev --extra evals pytest "${{ inputs.test_path }}" -v ;;
esac

- name: Upload reports
if: always()
uses: actions/upload-artifact@v4
with:
name: eval-reports-${{ github.run_id }}
path: backend/evals/reports/

- name: Comment on PR with results (if applicable)
if: always()
run: |
echo "TODO: gh pr comment with eval-summary diff"
84 changes: 84 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Tests & Fast Evals

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend

# Most service / scenario tests hit a real Postgres + Redis (the agent
# platform's encryption + per-user undo flows can't be faithfully
# exercised against fakes). Spin both up as job services and point the
# backend env at them; the `localhost` address resolves to the service
# via GitHub Actions' default networking.
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: archflow
POSTGRES_PASSWORD: archflow
POSTGRES_DB: archflow
ports: ["5432:5432"]
options: >-
--health-cmd "pg_isready -U archflow -d archflow"
--health-interval 5s
--health-timeout 5s
--health-retries 10
redis:
image: redis:7-alpine
ports: ["6379:6379"]
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 5s
--health-retries 10

env:
DATABASE_URL: postgresql+asyncpg://archflow:archflow@localhost:5432/archflow
DATABASE_URL_SYNC: postgresql://archflow:archflow@localhost:5432/archflow
REDIS_URL: redis://localhost:6379/0
JWT_SECRET: test-secret-not-for-production

steps:
- uses: actions/checkout@v4

- uses: astral-sh/setup-uv@v3
with:
version: latest

- name: Set up Python
run: uv python install 3.12

- name: Install deps
run: uv sync --frozen --extra agents --extra dev --extra evals

# Generate a throwaway Fernet key so agents code that wraps secrets
# at rest doesn't fail at import time. Real deployments set this in
# their environment; CI just needs *something* valid.
- name: Generate AGENTS_SECRET_KEY
run: |
KEY=$(uv run python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())")
echo "AGENTS_SECRET_KEY=$KEY" >> "$GITHUB_ENV"

# No explicit `alembic upgrade` step: backend/conftest.py auto-derives
# an `archflow_test` sibling DB, creates it if missing, and migrates
# it on session start. This is the same code path that protects the
# local dev DB from being truncated by accident.
- name: Unit tests
run: uv run pytest tests/ -v

# uv treats this project as a virtual workspace ("source = virtual"),
# which means `evals` isn't materialised in site-packages even though
# setuptools packages.find lists it. Put backend/ on PYTHONPATH so
# the eval conftest's `from evals.lib.judge import ...` resolves.
- name: Fast eval suite (deterministic, no LLM cost)
env:
PYTHONPATH: ${{ github.workspace }}/backend
run: make -C evals fast
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ frontend/src/api/generated/
# Keep our shared frontend lib/ despite a possible global "lib/" ignore rule
!frontend/src/lib/
!frontend/src/lib/**
# Same exception for the backend eval helpers (judge, agent_helpers, etc.) —
# the global `lib/` rule was hiding the entire `backend/evals/lib/` package
# from git, which then broke CI's eval suite with ModuleNotFoundError.
# The `__pycache__` re-ignore below stops the wildcard exception from
# accidentally tracking compiled bytecode.
!backend/evals/lib/
!backend/evals/lib/**
backend/evals/lib/**/__pycache__/

# Environment
.env
Expand Down Expand Up @@ -48,3 +56,7 @@ Thumbs.db

# Taskmaster (local planning / session state)
.taskmaster/

# Temporary working files (specs, scratch) — never commit
tmp/
ArchFlow.iml
8 changes: 0 additions & 8 deletions ArchFlow.iml

This file was deleted.

13 changes: 11 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
.PHONY: dev dev-deps dev-infra dev-backend dev-frontend setup test test-backend test-frontend build up down db-migrate db-upgrade db-downgrade db-sweep-undo api-codegen lint
.PHONY: dev dev-deps dev-infra dev-backend dev-frontend kill-dev setup test test-backend test-frontend build up down db-migrate db-upgrade db-downgrade db-sweep-undo api-codegen lint

# ─── Development ───────────────────────────────────────────────

dev: dev-deps dev-infra db-upgrade
@echo "Starting backend and frontend..."
@trap 'kill 0' EXIT; \
@trap 'kill 0 2>/dev/null; pids=$$(lsof -ti tcp:8000,5173 2>/dev/null); [ -n "$$pids" ] && kill -9 $$pids 2>/dev/null; exit 0' INT TERM EXIT; \
$(MAKE) dev-backend & \
$(MAKE) dev-frontend & \
wait
Expand All @@ -17,12 +17,21 @@ dev-deps:
dev-infra:
docker compose -f docker/docker-compose.dev.yml up -d

# Pre-kill anything still bound to 8000 — uvicorn --reload sometimes orphans
# its worker on Ctrl+C while serving an SSE stream, leaving the port held.
dev-backend:
-@pids=$$(lsof -ti tcp:8000 2>/dev/null); [ -n "$$pids" ] && kill -9 $$pids 2>/dev/null; true
cd backend && uv run uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

dev-frontend:
-@pids=$$(lsof -ti tcp:5173 2>/dev/null); [ -n "$$pids" ] && kill -9 $$pids 2>/dev/null; true
cd frontend && npm run dev

# Manual nuke — frees both dev ports without restarting.
kill-dev:
-@pids=$$(lsof -ti tcp:8000,5173 2>/dev/null); [ -n "$$pids" ] && kill -9 $$pids 2>/dev/null; true
@echo "Ports 8000 and 5173 freed."

setup: dev-deps dev-infra
@echo "Running initial setup..."
cd backend && uv run alembic revision --autogenerate -m "initial schema"
Expand Down
34 changes: 31 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,18 @@ L3 Component
- **Pinned / Recent** on the Overview dashboard.
- Full-text search across all objects and diagrams (⌘K / Ctrl+K).

### 🤖 AI agents
- **Multi-agent supervisor** orchestrating specialized sub-agents (planner, researcher, diagram, critic) over a LangGraph state machine — handles "describe this", "build me X", "review this design" inside the chat panel.
- **GitHub Repo Researcher** — link any Container/System to a GitHub URL and a read-only sub-agent fetches code, READMEs, issues, PRs, commits, and diffs to ground its answers in the actual implementation. Per-workspace GitHub PAT (encrypted at rest); 9 tools with per-turn LRU cache.
- **Diagram Explainer** — one-click natural-language summary of any object or connection, with inline popovers.
- **Provider-agnostic LLMs** via LiteLLM — pick OpenAI, Anthropic, OpenRouter, or any OpenAI-compatible endpoint per workspace; model + base URL stored encrypted.
- **Tool-call streaming UI** — live tool icons, sub-agent transitions, applied-change pills, and full transcripts that survive page reloads.
- **Optional Langfuse tracing** — per-workspace consent (`off` / `errors_only` / `full`).

### 🔌 Extensibility
- **REST API** (OpenAPI / Swagger UI at `/docs`) + orval-generated TypeScript client.
- **API keys** with prefix-based detection (`ak_…`), first-class citizens alongside JWT.
- **Webhooks** for `object.*`, `connection.*`, `diagram.*`, and more.
- Optional **AI insights** (Claude) — summarize an object's role, spot missing connections.
- **JSON export / import** for migration or CI snapshotting.

### 🌐 Realtime collaboration
Expand All @@ -97,6 +104,7 @@ L3 Component
- Alembic migrations
- PostgreSQL 16
- Redis (realtime fanout)
- LangGraph + LiteLLM (agents)
- pytest + pytest-asyncio
- uv package manager

Expand Down Expand Up @@ -247,10 +255,30 @@ DATABASE_URL=postgresql+asyncpg://archflow:archflow@localhost:5432/archflow
JWT_SECRET=change-me-in-production
BACKEND_CORS_ORIGINS=http://localhost:5173

# Optional — enables AI insights on ModelObjects
ANTHROPIC_API_KEY=sk-ant-...
# Optional — Langfuse tracing for agent calls (per-workspace consent gates each call).
LANGFUSE_PUBLIC_KEY=
LANGFUSE_SECRET_KEY=
LANGFUSE_HOST=
```

### ⚠️ Required for AI agents: `AGENTS_SECRET_KEY`

If you want the AI agent features (supervisor, repo researcher, diagram explainer) to work, you **must** set `AGENTS_SECRET_KEY` in `.env`. It's the symmetric Fernet key that encrypts every workspace's stored LLM provider API key and GitHub PAT at rest.

**Without it:**
- Saving a workspace LLM key → 500 error → no agent can reach an LLM
- Saving a GitHub PAT → 500 error → repo researcher can't read repos

Generate **once per deployment** and store like any other secret:

```bash
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
```

> 🛑 **Don't rotate it after secrets are saved.** There's no automatic re-encryption — losing this key locks every workspace's LLM and GitHub credentials forever. Back it up alongside `JWT_SECRET`.

LLM provider keys (OpenAI / Anthropic / OpenRouter / …) and the GitHub PAT for the repo-researcher are stored **per-workspace** in the database (encrypted by `AGENTS_SECRET_KEY`) — not in `.env`. Configure them from the workspace Settings page.

---

## 🐛 Troubleshooting
Expand Down
5 changes: 2 additions & 3 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ FROM python:3.12-slim AS builder

WORKDIR /app
COPY pyproject.toml .
COPY . .

RUN pip install uv && \
uv pip install --system -r pyproject.toml

COPY . .
uv pip install --system ".[agents]"

FROM python:3.12-slim

Expand Down
43 changes: 40 additions & 3 deletions backend/alembic/versions/91e6520f52f4_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,47 @@


def upgrade() -> None:
"""Upgrade schema."""
pass
"""Upgrade schema.

Mirrors ``app.models.notification.Notification`` (UUIDMixin + TimestampMixin
+ per-user notification fields). The original revision shipped empty,
which only worked when the schema was bootstrapped via
``Base.metadata.create_all`` outside Alembic. Restoring the real CREATE
so a clean ``alembic upgrade head`` builds a working schema.
"""
op.create_table(
"notifications",
sa.Column("id", sa.dialects.postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column(
"user_id",
sa.dialects.postgresql.UUID(as_uuid=True),
sa.ForeignKey("users.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("kind", sa.String(64), nullable=False),
sa.Column("title", sa.String(255), nullable=False),
sa.Column("body", sa.Text(), nullable=True),
sa.Column("target_url", sa.String(512), nullable=True),
sa.Column("read_at", sa.DateTime(timezone=True), nullable=True),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
server_default=sa.func.now(),
nullable=False,
),
sa.Column(
"updated_at",
sa.DateTime(timezone=True),
server_default=sa.func.now(),
nullable=False,
),
)
op.create_index(
"ix_notifications_user_id", "notifications", ["user_id"]
)


def downgrade() -> None:
"""Downgrade schema."""
pass
op.drop_index("ix_notifications_user_id", table_name="notifications")
op.drop_table("notifications")
Loading
Loading