Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
65ae940
Add SQLAlchemy models and Alembic setup
MomoSolaris8 Oct 23, 2026
dddf30a
Add memory models
MomoSolaris8 Oct 23, 2026
ed886c3
Add MCP models and compare server defaults
MomoSolaris8 Oct 23, 2026
2ac510a
Add claims models
MomoSolaris8 Oct 23, 2026
1327602
Add RAG chunk model
MomoSolaris8 Oct 23, 2026
63593ad
Add baseline migration
MomoSolaris8 Oct 23, 2026
4f82815
Add integration test database fixture
MomoSolaris8 Oct 23, 2026
ff02bee
Add uploads store integration tests
MomoSolaris8 Oct 23, 2026
28adf64
Use SQLAlchemy for uploads store
MomoSolaris8 Oct 23, 2026
fb9934e
Add auth flow integration tests
MomoSolaris8 Oct 23, 2026
1d1bd73
Use SQLAlchemy for auth store
MomoSolaris8 Oct 23, 2026
338133d
Add MCP store integration tests
MomoSolaris8 Oct 26, 2026
01b3f31
Cover MCP servers, credentials and audit log in store tests
MomoSolaris8 Oct 27, 2026
1dadddf
Seed MCP servers and tool policies in a migration
MomoSolaris8 Oct 27, 2026
db87b39
Require Python 3.11
MomoSolaris8 Oct 27, 2026
b6e3f71
Use SQLAlchemy for MCP store
MomoSolaris8 Oct 27, 2026
48aded3
Add memory service integration tests
MomoSolaris8 Oct 27, 2026
87d2c83
Use SQLAlchemy for memory store
MomoSolaris8 Oct 27, 2026
eb09bf9
Stop users from taking over other users' threads
MomoSolaris8 Oct 28, 2026
4dd666d
Stop users from taking over other users' threads
MomoSolaris8 Oct 28, 2026
46e2c46
Add claim service integration tests
MomoSolaris8 Oct 28, 2026
c62e9f7
Use SQLAlchemy for claim store
MomoSolaris8 Oct 28, 2026
9a09ce8
Add RAG index and retrieval integration tests
MomoSolaris8 Oct 28, 2026
5251977
Use SQLAlchemy for RAG store
MomoSolaris8 Oct 29, 2026
a2b988d
Add HNSW index on chunk embeddings
MomoSolaris8 Oct 29, 2026
9f63518
Run integration tests against Postgres in CI
MomoSolaris8 Oct 29, 2026
ac87cd6
Run database migrations before deploying backend
MomoSolaris8 Oct 29, 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
28 changes: 28 additions & 0 deletions .github/workflows/azure-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,34 @@ jobs:
echo "image_uri=${IMAGE_URI}" >> "$GITHUB_OUTPUT"
echo "Deploying ${IMAGE_URI}"

- name: Check out deployed commit
uses: actions/checkout@v4
with:
ref: ${{ github.event.workflow_run.head_sha || github.sha }}

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install migration dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -e .

- name: Run database migrations
shell: bash
run: |
DATABASE_URL="$(az containerapp secret show \
--name "$AZURE_CONTAINER_APP" \
--resource-group "$AZURE_RESOURCE_GROUP" \
--secret-name database-url \
--query value \
--output tsv)"
echo "::add-mask::${DATABASE_URL}"
export DATABASE_URL
alembic upgrade head

- name: Update Azure Container App
run: |
az containerapp update \
Expand Down
34 changes: 27 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,30 @@ jobs:
name: Backend tests
runs-on: ubuntu-latest

services:
postgres:
image: pgvector/pgvector:pg16
env:
POSTGRES_USER: supportagent
POSTGRES_PASSWORD: supportagent
POSTGRES_DB: supportagent
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U supportagent"
--health-interval 5s
--health-timeout 5s
--health-retries 10

env:
CHAT_MODEL: qwen-plus
CHAT_MODELS: qwen-plus,qwen-max,qwen3-max
EMBEDDING_API_KEY: ci-placeholder
EMBEDDING_BASE_URL: https://example.invalid/v1
DATABASE_URL: postgresql://supportagent:supportagent@localhost:5432/supportagent
TEST_DATABASE_URL: postgresql://supportagent:supportagent@localhost:5432/supportagent_test
MCP_DYNAMIC_TOOLS_ENABLED: "false"

steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -29,15 +53,11 @@ jobs:
run: python -m compileall -q src/backend/supportagent

- name: Run backend tests
env:
CHAT_MODEL: qwen-plus
CHAT_MODELS: qwen-plus,qwen-max,qwen3-max
EMBEDDING_API_KEY: ci-placeholder
EMBEDDING_BASE_URL: https://example.invalid/v1
DATABASE_URL: postgresql://supportagent:supportagent@localhost:5432/supportagent
MCP_DYNAMIC_TOOLS_ENABLED: "false"
run: python -m pytest

- name: Run integration tests
run: python -m pytest tests/integration --run-integration --deselect tests/integration/storage

- name: Run offline claim evaluation
run: |
python -m supportagent.evaluation \
Expand Down
149 changes: 149 additions & 0 deletions alembic.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# A generic, single database configuration.

[alembic]
# path to migration scripts.
# this is typically a path given in POSIX (e.g. forward slashes)
# format, relative to the token %(here)s which refers to the location of this
# ini file
script_location = %(here)s/migrations

# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
# Uncomment the line below if you want the files to be prepended with date and time
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
# for all available tokens
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
# Or organize into date-based subdirectories (requires recursive_version_locations = true)
# file_template = %%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s

# sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory. for multiple paths, the path separator
# is defined by "path_separator" below.
prepend_sys_path = .


# timezone to use when rendering the date within the migration file
# as well as the filename.
# If specified, requires the tzdata library which can be installed by adding
# `alembic[tz]` to the pip requirements.
# string value is passed to ZoneInfo()
# leave blank for localtime
# timezone =

# max length of characters to apply to the "slug" field
# truncate_slug_length = 40

# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false

# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false

# version location specification; This defaults
# to <script_location>/versions. When using multiple version
# directories, initial revisions must be specified with --version-path.
# The path separator used here should be the separator specified by "path_separator"
# below.
# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions

# path_separator; This indicates what character is used to split lists of file
# paths, including version_locations and prepend_sys_path within configparser
# files such as alembic.ini.
# The default rendered in new alembic.ini files is "os", which uses os.pathsep
# to provide os-dependent path splitting.
#
# Note that in order to support legacy alembic.ini files, this default does NOT
# take place if path_separator is not present in alembic.ini. If this
# option is omitted entirely, fallback logic is as follows:
#
# 1. Parsing of the version_locations option falls back to using the legacy
# "version_path_separator" key, which if absent then falls back to the legacy
# behavior of splitting on spaces and/or commas.
# 2. Parsing of the prepend_sys_path option falls back to the legacy
# behavior of splitting on spaces, commas, or colons.
#
# Valid values for path_separator are:
#
# path_separator = :
# path_separator = ;
# path_separator = space
# path_separator = newline
#
# Use os.pathsep. Default configuration used for new projects.
path_separator = os

# set to 'true' to search source files recursively
# in each "version_locations" directory
# new in Alembic version 1.10
# recursive_version_locations = false

# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8

# database URL. This is consumed by the user-maintained env.py script only.
# other means of configuring database URLs may be customized within the env.py
# file.
sqlalchemy.url =


[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples

# format using "black" - use the console_scripts runner, against the "black" entrypoint
# hooks = black
# black.type = console_scripts
# black.entrypoint = black
# black.options = -l 79 REVISION_SCRIPT_FILENAME

# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module
# hooks = ruff
# ruff.type = module
# ruff.module = ruff
# ruff.options = check --fix REVISION_SCRIPT_FILENAME

# Alternatively, use the exec runner to execute a binary found on your PATH
# hooks = ruff
# ruff.type = exec
# ruff.executable = ruff
# ruff.options = check --fix REVISION_SCRIPT_FILENAME

# Logging configuration. This is also consumed by the user-maintained
# env.py script only.
[loggers]
keys = root,sqlalchemy,alembic

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = WARNING
handlers = console
qualname =

[logger_sqlalchemy]
level = WARNING
handlers =
qualname = sqlalchemy.engine

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
1 change: 1 addition & 0 deletions migrations/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Generic single-database configuration.
28 changes: 28 additions & 0 deletions migrations/env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from logging.config import fileConfig

from alembic import context

from supportagent.db import models # noqa: F401
from supportagent.db.base import Base
from supportagent.db.session import engine

config = context.config
if config.config_file_name is not None:
fileConfig(config.config_file_name)

target_metadata = Base.metadata


def run_migrations_online() -> None:
with engine.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
compare_type=True,
compare_server_default=True,
)
with context.begin_transaction():
context.run_migrations()


run_migrations_online()
29 changes: 29 additions & 0 deletions migrations/script.py.mako
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""${message}

Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
import pgvector.sqlalchemy # noqa: F401
${imports if imports else ""}

# revision identifiers, used by Alembic.
revision: str = ${repr(up_revision)}
down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}


def upgrade() -> None:
"""Upgrade schema."""
${upgrades if upgrades else "pass"}


def downgrade() -> None:
"""Downgrade schema."""
${downgrades if downgrades else "pass"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Add HNSW index on chunk embeddings

Revision ID: 0fdad45ad4f7
Revises: 267f87b25a77
Create Date: 2026-10-29 15:22:51.359910

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
import pgvector.sqlalchemy # noqa: F401


# revision identifiers, used by Alembic.
revision: str = '0fdad45ad4f7'
down_revision: Union[str, Sequence[str], None] = '267f87b25a77'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_index('chunks_embedding_hnsw_idx', 'chunks', ['embedding'], unique=False, postgresql_using='hnsw', postgresql_ops={'embedding': 'vector_cosine_ops'})
# ### end Alembic commands ###


def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index('chunks_embedding_hnsw_idx', table_name='chunks', postgresql_using='hnsw', postgresql_ops={'embedding': 'vector_cosine_ops'})
# ### end Alembic commands ###
Loading
Loading