Files
Alex 574f96341e refactor: rename the application package to docsgpt
The backend import package is now docsgpt, the name it will carry on PyPI;
application was far too generic to install into anyone's site-packages.
git mv plus a mechanical rewrite of every import, dotted string and path
reference: 734 Python files, the compose files, Dockerfile, workflows, docs,
setup scripts, devcontainer, k8s manifests, vscode config, pytest and coverage
config, .gitignore. Behaviour is unchanged.

Kept for one release:
- A top-level application package whose meta-path finder resolves
  application.x.y to the already-imported docsgpt.x.y object, so old imports
  and entry points (celery -A application.app.celery,
  uvicorn application.asgi:asgi_app) keep working with a FutureWarning.
- Celery registers every application.* task name as an alias of its
  docsgpt.* task on start-up, so messages queued by the previous release still
  run. The redbeat key prefix moves to redbeat:docsgpt:v2: so schedule entries
  the previous release wrote are left unread instead of firing twice.

The backend image builds from the repository root (docker build -f
docsgpt/Dockerfile .) so it can ship the alias package; a root .dockerignore
allow-lists docsgpt/ and application/ and keeps caches, local data, .env
files, the sample index files and the Dockerfile out. Compose and the image
workflows point at the new context.
2026-09-07 10:20:43 +01:00

112 lines
4.6 KiB
Python

"""0025 artifacts — append-only artifact identity + version store.
Adds the long-anticipated ``artifacts`` entity referenced by the schema header
in ``models.py`` but never built. Two tables:
* ``artifacts`` — one identity row per logical artifact. The stable ``id`` is the
handle passed around (chat/workflow ``state``/message bodies carry only this
reference, never bytes). ``current_version`` mirrors
``workflows.current_graph_version`` (atomic increment on each new version).
Authz is parent-derived: whoever can reach the ``conversation_id`` (chat) or
``workflow_run_id`` (run) can reach its artifacts, so a CHECK requires at least
one parent. ``user_id`` is kept for ownership/quota only; ``team_id`` is a
nullable forward-compat hook for the in-flight Teams work (no FK, matching how
``teams`` itself leaves owner/member columns FK-free).
* ``artifact_versions`` — append-only, never mutated. Each edit appends a row and
bumps ``artifacts.current_version``. ``UNIQUE(artifact_id, version)`` enforces
monotonic, gap-checkable versions. ``storage_path`` is the ``BaseStorage`` key
(NULL when the version is spec-only). Pass-by-reference: bytes live in storage,
only metadata + the key live here.
Revision ID: 0025_artifacts
Revises: 0024_wiki_pages_updated_via
"""
from typing import Sequence, Union
from alembic import op
revision: str = "0025_artifacts"
down_revision: Union[str, None] = "0024_wiki_pages_updated_via"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# --- artifacts -----------------------------------------------------------
op.execute(
"""
CREATE TABLE IF NOT EXISTS artifacts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id TEXT NOT NULL,
conversation_id UUID,
workflow_run_id UUID,
team_id UUID,
message_id UUID,
kind TEXT NOT NULL,
title TEXT,
metadata JSONB,
current_version INTEGER NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT artifacts_parent_present_check
CHECK (conversation_id IS NOT NULL OR workflow_run_id IS NOT NULL)
);
"""
)
op.execute("CREATE INDEX IF NOT EXISTS artifacts_user_idx ON artifacts (user_id);")
op.execute(
"CREATE INDEX IF NOT EXISTS artifacts_conversation_idx "
"ON artifacts (conversation_id) WHERE conversation_id IS NOT NULL;"
)
op.execute(
"CREATE INDEX IF NOT EXISTS artifacts_workflow_run_idx "
"ON artifacts (workflow_run_id) WHERE workflow_run_id IS NOT NULL;"
)
# Reuse the shared set_updated_at() trigger fn defined in 0001.
op.execute("DROP TRIGGER IF EXISTS artifacts_set_updated_at ON artifacts;")
op.execute(
"""
CREATE TRIGGER artifacts_set_updated_at
BEFORE UPDATE ON artifacts
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
"""
)
# --- artifact_versions ---------------------------------------------------
op.execute(
"""
CREATE TABLE IF NOT EXISTS artifact_versions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
artifact_id UUID NOT NULL REFERENCES artifacts(id) ON DELETE CASCADE,
version INTEGER NOT NULL,
mime_type TEXT,
filename TEXT,
storage_path TEXT,
size BIGINT,
sha256 TEXT,
spec JSONB,
preview_text TEXT,
produced_by JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT artifact_versions_artifact_version_uidx
UNIQUE (artifact_id, version)
);
"""
)
op.execute(
"CREATE INDEX IF NOT EXISTS artifact_versions_artifact_idx "
"ON artifact_versions (artifact_id);"
)
def downgrade() -> None:
op.execute("DROP INDEX IF EXISTS artifact_versions_artifact_idx;")
op.execute("DROP TABLE IF EXISTS artifact_versions;")
op.execute("DROP TRIGGER IF EXISTS artifacts_set_updated_at ON artifacts;")
op.execute("DROP INDEX IF EXISTS artifacts_workflow_run_idx;")
op.execute("DROP INDEX IF EXISTS artifacts_conversation_idx;")
op.execute("DROP INDEX IF EXISTS artifacts_user_idx;")
op.execute("DROP TABLE IF EXISTS artifacts;")