vecshift 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vecshift-0.1.0/.github/workflows/ci.yml +146 -0
- vecshift-0.1.0/.github/workflows/release.yml +146 -0
- vecshift-0.1.0/.gitignore +33 -0
- vecshift-0.1.0/CHANGELOG.md +140 -0
- vecshift-0.1.0/Dockerfile +35 -0
- vecshift-0.1.0/LICENSE +202 -0
- vecshift-0.1.0/NOTICE +4 -0
- vecshift-0.1.0/PKG-INFO +264 -0
- vecshift-0.1.0/README.md +228 -0
- vecshift-0.1.0/RELEASING.md +75 -0
- vecshift-0.1.0/SECURITY.md +30 -0
- vecshift-0.1.0/demo/compose.yaml +42 -0
- vecshift-0.1.0/demo/out/.gitignore +2 -0
- vecshift-0.1.0/demo/run.sh +60 -0
- vecshift-0.1.0/demo/seed.py +254 -0
- vecshift-0.1.0/docs/architecture.md +183 -0
- vecshift-0.1.0/docs/bench.md +128 -0
- vecshift-0.1.0/docs/connectors/pgvector.md +120 -0
- vecshift-0.1.0/docs/demo.md +76 -0
- vecshift-0.1.0/docs/eval.md +129 -0
- vecshift-0.1.0/docs/images/doctor-report.png +0 -0
- vecshift-0.1.0/docs/images/eval-report.png +0 -0
- vecshift-0.1.0/docs/images/logo/vecshift-dark.svg +1 -0
- vecshift-0.1.0/docs/images/logo/vecshift-light.svg +1 -0
- vecshift-0.1.0/docs/images/logo/vecshift-mark-dark.svg +1 -0
- vecshift-0.1.0/docs/images/logo/vecshift-mark-light.svg +1 -0
- vecshift-0.1.0/docs/images/logo/vecshift-mark.svg +1 -0
- vecshift-0.1.0/docs/images/social-preview.png +0 -0
- vecshift-0.1.0/docs/migrations.md +328 -0
- vecshift-0.1.0/docs/prior-art.md +44 -0
- vecshift-0.1.0/docs/roadmap.md +125 -0
- vecshift-0.1.0/docs/security.md +116 -0
- vecshift-0.1.0/pyproject.toml +130 -0
- vecshift-0.1.0/scripts/release_notes.py +42 -0
- vecshift-0.1.0/src/vecshift/__init__.py +15 -0
- vecshift-0.1.0/src/vecshift/assets/bench.css +39 -0
- vecshift-0.1.0/src/vecshift/assets/eval.css +91 -0
- vecshift-0.1.0/src/vecshift/assets/report.css +252 -0
- vecshift-0.1.0/src/vecshift/assets/report.js +50 -0
- vecshift-0.1.0/src/vecshift/bench/__init__.py +18 -0
- vecshift-0.1.0/src/vecshift/bench/corpus.py +175 -0
- vecshift-0.1.0/src/vecshift/bench/generate.py +106 -0
- vecshift-0.1.0/src/vecshift/bench/html.py +325 -0
- vecshift-0.1.0/src/vecshift/bench/metrics.py +50 -0
- vecshift-0.1.0/src/vecshift/bench/runner.py +183 -0
- vecshift-0.1.0/src/vecshift/cli.py +236 -0
- vecshift-0.1.0/src/vecshift/cli_apply.py +283 -0
- vecshift-0.1.0/src/vecshift/cli_bench.py +354 -0
- vecshift-0.1.0/src/vecshift/cli_cutover.py +431 -0
- vecshift-0.1.0/src/vecshift/cli_eval.py +591 -0
- vecshift-0.1.0/src/vecshift/cli_plan.py +335 -0
- vecshift-0.1.0/src/vecshift/cli_style.py +57 -0
- vecshift-0.1.0/src/vecshift/connectors/__init__.py +1 -0
- vecshift-0.1.0/src/vecshift/connectors/pgvector/__init__.py +29 -0
- vecshift-0.1.0/src/vecshift/connectors/pgvector/connection.py +155 -0
- vecshift-0.1.0/src/vecshift/connectors/pgvector/documents.py +96 -0
- vecshift-0.1.0/src/vecshift/connectors/pgvector/inspect.py +427 -0
- vecshift-0.1.0/src/vecshift/connectors/pgvector/search.py +240 -0
- vecshift-0.1.0/src/vecshift/connectors/pgvector/switch.py +481 -0
- vecshift-0.1.0/src/vecshift/connectors/pgvector/target.py +195 -0
- vecshift-0.1.0/src/vecshift/connectors/pgvector/writer.py +431 -0
- vecshift-0.1.0/src/vecshift/core/__init__.py +4 -0
- vecshift-0.1.0/src/vecshift/core/capabilities.py +33 -0
- vecshift-0.1.0/src/vecshift/core/contracts.py +57 -0
- vecshift-0.1.0/src/vecshift/core/fingerprint.py +57 -0
- vecshift-0.1.0/src/vecshift/core/record.py +75 -0
- vecshift-0.1.0/src/vecshift/doctor/__init__.py +15 -0
- vecshift-0.1.0/src/vecshift/doctor/checks.py +490 -0
- vecshift-0.1.0/src/vecshift/doctor/findings.py +78 -0
- vecshift-0.1.0/src/vecshift/doctor/html.py +493 -0
- vecshift-0.1.0/src/vecshift/doctor/profile.py +69 -0
- vecshift-0.1.0/src/vecshift/embeddings/__init__.py +22 -0
- vecshift-0.1.0/src/vecshift/embeddings/cache.py +86 -0
- vecshift-0.1.0/src/vecshift/embeddings/providers.py +244 -0
- vecshift-0.1.0/src/vecshift/embeddings/spec.py +240 -0
- vecshift-0.1.0/src/vecshift/eval/__init__.py +20 -0
- vecshift-0.1.0/src/vecshift/eval/html.py +444 -0
- vecshift-0.1.0/src/vecshift/eval/metrics.py +81 -0
- vecshift-0.1.0/src/vecshift/eval/queries.py +97 -0
- vecshift-0.1.0/src/vecshift/eval/runner.py +394 -0
- vecshift-0.1.0/src/vecshift/html_kit.py +143 -0
- vecshift-0.1.0/src/vecshift/jobs/__init__.py +5 -0
- vecshift-0.1.0/src/vecshift/jobs/spec.py +202 -0
- vecshift-0.1.0/src/vecshift/migrate/__init__.py +6 -0
- vecshift-0.1.0/src/vecshift/migrate/engine.py +272 -0
- vecshift-0.1.0/src/vecshift/migrate/state.py +50 -0
- vecshift-0.1.0/src/vecshift/planning/__init__.py +14 -0
- vecshift-0.1.0/src/vecshift/planning/plan.py +87 -0
- vecshift-0.1.0/src/vecshift/planning/planner.py +493 -0
- vecshift-0.1.0/src/vecshift/py.typed +0 -0
- vecshift-0.1.0/tests/__init__.py +0 -0
- vecshift-0.1.0/tests/integration/__init__.py +0 -0
- vecshift-0.1.0/tests/integration/conftest.py +69 -0
- vecshift-0.1.0/tests/integration/test_pgvector_apply.py +340 -0
- vecshift-0.1.0/tests/integration/test_pgvector_bench.py +49 -0
- vecshift-0.1.0/tests/integration/test_pgvector_cutover.py +500 -0
- vecshift-0.1.0/tests/integration/test_pgvector_doctor.py +342 -0
- vecshift-0.1.0/tests/integration/test_pgvector_eval.py +280 -0
- vecshift-0.1.0/tests/integration/test_pgvector_plan.py +211 -0
- vecshift-0.1.0/tests/test_bench.py +361 -0
- vecshift-0.1.0/tests/test_cli.py +36 -0
- vecshift-0.1.0/tests/test_contracts.py +42 -0
- vecshift-0.1.0/tests/test_doctor_checks.py +169 -0
- vecshift-0.1.0/tests/test_doctor_html.py +136 -0
- vecshift-0.1.0/tests/test_embeddings.py +254 -0
- vecshift-0.1.0/tests/test_eval.py +273 -0
- vecshift-0.1.0/tests/test_eval_html.py +83 -0
- vecshift-0.1.0/tests/test_fingerprint.py +56 -0
- vecshift-0.1.0/tests/test_jobs.py +59 -0
- vecshift-0.1.0/tests/test_migrate.py +304 -0
- vecshift-0.1.0/tests/test_pgvector_connection.py +83 -0
- vecshift-0.1.0/tests/test_planner.py +203 -0
- vecshift-0.1.0/tests/test_record.py +47 -0
- vecshift-0.1.0/tests/test_release.py +47 -0
- vecshift-0.1.0/tests/test_security.py +352 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
schedule:
|
|
8
|
+
# Weekly, so newly published vulnerabilities in dependencies are caught between commits.
|
|
9
|
+
- cron: "17 6 * * 1"
|
|
10
|
+
|
|
11
|
+
# Least privilege: jobs can read the repository and nothing else.
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
concurrency:
|
|
16
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
17
|
+
cancel-in-progress: true
|
|
18
|
+
|
|
19
|
+
# Actions are pinned to full commit SHAs so a moved or compromised tag can't change what
|
|
20
|
+
# runs here. Dependabot keeps the pins (and the version comments) up to date.
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
lint:
|
|
24
|
+
name: Lint and type check
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
timeout-minutes: 10
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
29
|
+
with:
|
|
30
|
+
persist-credentials: false
|
|
31
|
+
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
|
32
|
+
- run: uv sync --locked
|
|
33
|
+
- run: uv run ruff check .
|
|
34
|
+
- run: uv run ruff format --check .
|
|
35
|
+
- run: uv run mypy
|
|
36
|
+
|
|
37
|
+
test:
|
|
38
|
+
name: Test (Python ${{ matrix.python-version }})
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
timeout-minutes: 15
|
|
41
|
+
strategy:
|
|
42
|
+
fail-fast: false
|
|
43
|
+
matrix:
|
|
44
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
45
|
+
env:
|
|
46
|
+
UV_PYTHON: ${{ matrix.python-version }}
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
49
|
+
with:
|
|
50
|
+
persist-credentials: false
|
|
51
|
+
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
|
52
|
+
- run: uv sync --locked
|
|
53
|
+
- run: uv run pytest --cov=vecshift --cov-report=term-missing
|
|
54
|
+
|
|
55
|
+
integration:
|
|
56
|
+
name: Integration (PostgreSQL ${{ matrix.postgres }} + pgvector)
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
timeout-minutes: 15
|
|
59
|
+
strategy:
|
|
60
|
+
fail-fast: false
|
|
61
|
+
matrix:
|
|
62
|
+
postgres: ["16", "17"]
|
|
63
|
+
services:
|
|
64
|
+
postgres:
|
|
65
|
+
# Docker Hub's mirror on Google: the same images (identical digests), without Docker
|
|
66
|
+
# Hub's rate limit on anonymous pulls from CI runners.
|
|
67
|
+
image: mirror.gcr.io/pgvector/pgvector:pg${{ matrix.postgres }}
|
|
68
|
+
env:
|
|
69
|
+
# A throwaway database that exists only for this job.
|
|
70
|
+
POSTGRES_PASSWORD: postgres
|
|
71
|
+
ports:
|
|
72
|
+
- 5432:5432
|
|
73
|
+
options: >-
|
|
74
|
+
--health-cmd "pg_isready -U postgres"
|
|
75
|
+
--health-interval 5s
|
|
76
|
+
--health-timeout 5s
|
|
77
|
+
--health-retries 10
|
|
78
|
+
env:
|
|
79
|
+
VECSHIFT_TEST_PG_DSN: postgresql://postgres:postgres@localhost:5432/postgres
|
|
80
|
+
steps:
|
|
81
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
82
|
+
with:
|
|
83
|
+
persist-credentials: false
|
|
84
|
+
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
|
85
|
+
- run: uv sync --locked
|
|
86
|
+
- run: uv run pytest tests/integration
|
|
87
|
+
|
|
88
|
+
security:
|
|
89
|
+
name: Security audit
|
|
90
|
+
runs-on: ubuntu-latest
|
|
91
|
+
timeout-minutes: 10
|
|
92
|
+
steps:
|
|
93
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
94
|
+
with:
|
|
95
|
+
persist-credentials: false
|
|
96
|
+
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
|
97
|
+
- name: Known vulnerabilities in locked dependencies
|
|
98
|
+
run: |
|
|
99
|
+
uv export --frozen --all-extras --all-groups --no-hashes --no-emit-project -o requirements-audit.txt
|
|
100
|
+
uvx pip-audit==2.10.1 --strict -r requirements-audit.txt
|
|
101
|
+
- name: GitHub Actions workflow security
|
|
102
|
+
run: uvx zizmor==1.30.1 --offline .github/workflows/
|
|
103
|
+
|
|
104
|
+
package:
|
|
105
|
+
name: Package
|
|
106
|
+
runs-on: ubuntu-latest
|
|
107
|
+
timeout-minutes: 10
|
|
108
|
+
steps:
|
|
109
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
110
|
+
with:
|
|
111
|
+
persist-credentials: false
|
|
112
|
+
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
|
113
|
+
- run: uv build
|
|
114
|
+
- name: PyPI metadata and README render
|
|
115
|
+
run: uvx twine==7.0.0 check --strict dist/*
|
|
116
|
+
- name: The wheel installs and runs on its own
|
|
117
|
+
run: |
|
|
118
|
+
uv venv /tmp/wheel-test
|
|
119
|
+
uv pip install --python /tmp/wheel-test/bin/python dist/*.whl
|
|
120
|
+
/tmp/wheel-test/bin/vecshift --version
|
|
121
|
+
/tmp/wheel-test/bin/python -c "from importlib.resources import files; assert files('vecshift').joinpath('assets/report.js').is_file()"
|
|
122
|
+
|
|
123
|
+
demo:
|
|
124
|
+
name: Docker image and demo
|
|
125
|
+
runs-on: ubuntu-latest
|
|
126
|
+
timeout-minutes: 15
|
|
127
|
+
env:
|
|
128
|
+
# Docker Hub's images through Google's mirror (same digests), to avoid rate limits.
|
|
129
|
+
PYTHON_IMAGE: mirror.gcr.io/library/python:3.13-slim@sha256:70729b46c69b4f1e97c4822c1af3df53a1476cf5ddc6c087c0c10bc3a5678c2f
|
|
130
|
+
PGVECTOR_IMAGE: mirror.gcr.io/pgvector/pgvector:pg17@sha256:ac08538c6f8b9904c33c8224c5e5706dbe760aca29db1d096972b4052c22a75d
|
|
131
|
+
steps:
|
|
132
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
133
|
+
with:
|
|
134
|
+
persist-credentials: false
|
|
135
|
+
- name: Run the offline demo end to end
|
|
136
|
+
working-directory: demo
|
|
137
|
+
run: |
|
|
138
|
+
chmod a+w out
|
|
139
|
+
docker compose up --build --abort-on-container-exit --exit-code-from vecshift
|
|
140
|
+
test -s out/eval.html
|
|
141
|
+
- name: Roll back
|
|
142
|
+
working-directory: demo
|
|
143
|
+
run: docker compose run --rm --entrypoint vecshift vecshift rollback --yes
|
|
144
|
+
- if: always()
|
|
145
|
+
working-directory: demo
|
|
146
|
+
run: docker compose down --volumes
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Pushing a tag such as v0.1.0 publishes that version: the package to PyPI, the image to
|
|
4
|
+
# GitHub Container Registry, and a GitHub release with the CHANGELOG section as notes.
|
|
5
|
+
# See RELEASING.md.
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags: ["v[0-9]+.[0-9]+.[0-9]+*"]
|
|
10
|
+
|
|
11
|
+
# No permissions by default; each job asks for only what it needs.
|
|
12
|
+
permissions: {}
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: release
|
|
16
|
+
cancel-in-progress: false
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
build:
|
|
20
|
+
name: Check and build
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
timeout-minutes: 15
|
|
23
|
+
permissions:
|
|
24
|
+
contents: read
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
27
|
+
with:
|
|
28
|
+
persist-credentials: false
|
|
29
|
+
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
|
30
|
+
with:
|
|
31
|
+
# A release never restores a cache that another workflow could have written.
|
|
32
|
+
enable-cache: false
|
|
33
|
+
- run: uv sync --locked
|
|
34
|
+
- run: uv run ruff check .
|
|
35
|
+
- run: uv run ruff format --check .
|
|
36
|
+
- run: uv run mypy
|
|
37
|
+
- run: uv run pytest
|
|
38
|
+
- run: uv build
|
|
39
|
+
- name: The tag matches the package version
|
|
40
|
+
run: |
|
|
41
|
+
version="${GITHUB_REF_NAME#v}"
|
|
42
|
+
if [ ! -f "dist/vecshift-${version}-py3-none-any.whl" ]; then
|
|
43
|
+
echo "::error::Tag ${GITHUB_REF_NAME} doesn't match the version in src/vecshift/__init__.py:"
|
|
44
|
+
ls dist
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
- name: CHANGELOG.md has notes for this version
|
|
48
|
+
run: python scripts/release_notes.py "$GITHUB_REF_NAME" > /dev/null
|
|
49
|
+
- run: uvx twine==7.0.0 check --strict dist/*
|
|
50
|
+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
51
|
+
with:
|
|
52
|
+
name: dist
|
|
53
|
+
path: dist/
|
|
54
|
+
if-no-files-found: error
|
|
55
|
+
|
|
56
|
+
pypi:
|
|
57
|
+
name: Publish to PyPI
|
|
58
|
+
needs: build
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
timeout-minutes: 10
|
|
61
|
+
# Trusted publishing: PyPI accepts this job's OpenID Connect token, so no API token is
|
|
62
|
+
# stored anywhere. The environment can require a maintainer's approval.
|
|
63
|
+
environment:
|
|
64
|
+
name: pypi
|
|
65
|
+
url: https://pypi.org/project/vecshift/
|
|
66
|
+
permissions:
|
|
67
|
+
id-token: write # trusted publishing to PyPI
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
70
|
+
with:
|
|
71
|
+
name: dist
|
|
72
|
+
path: dist/
|
|
73
|
+
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
74
|
+
|
|
75
|
+
image:
|
|
76
|
+
name: Publish the Docker image
|
|
77
|
+
needs: build
|
|
78
|
+
runs-on: ubuntu-latest
|
|
79
|
+
timeout-minutes: 30
|
|
80
|
+
permissions:
|
|
81
|
+
contents: read
|
|
82
|
+
packages: write # push the image to ghcr.io
|
|
83
|
+
id-token: write # sign the provenance attestation
|
|
84
|
+
attestations: write # store it
|
|
85
|
+
steps:
|
|
86
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
87
|
+
with:
|
|
88
|
+
persist-credentials: false
|
|
89
|
+
- uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.0
|
|
90
|
+
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
|
91
|
+
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
|
92
|
+
with:
|
|
93
|
+
registry: ghcr.io
|
|
94
|
+
username: ${{ github.actor }}
|
|
95
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
96
|
+
- id: meta
|
|
97
|
+
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
|
98
|
+
with:
|
|
99
|
+
images: ghcr.io/${{ github.repository }}
|
|
100
|
+
tags: |
|
|
101
|
+
type=semver,pattern={{version}}
|
|
102
|
+
type=semver,pattern={{major}}.{{minor}}
|
|
103
|
+
- id: push
|
|
104
|
+
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
|
|
105
|
+
with:
|
|
106
|
+
context: .
|
|
107
|
+
# Docker Hub's base image through Google's mirror (same digest), to avoid rate limits.
|
|
108
|
+
build-args: |
|
|
109
|
+
PYTHON_IMAGE=mirror.gcr.io/library/python:3.13-slim@sha256:70729b46c69b4f1e97c4822c1af3df53a1476cf5ddc6c087c0c10bc3a5678c2f
|
|
110
|
+
platforms: linux/amd64,linux/arm64
|
|
111
|
+
push: true
|
|
112
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
113
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
114
|
+
provenance: mode=max
|
|
115
|
+
sbom: true
|
|
116
|
+
- uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
|
117
|
+
with:
|
|
118
|
+
subject-name: ghcr.io/${{ github.repository }}
|
|
119
|
+
subject-digest: ${{ steps.push.outputs.digest }}
|
|
120
|
+
push-to-registry: true
|
|
121
|
+
|
|
122
|
+
github:
|
|
123
|
+
name: GitHub release
|
|
124
|
+
needs: [pypi, image]
|
|
125
|
+
runs-on: ubuntu-latest
|
|
126
|
+
timeout-minutes: 10
|
|
127
|
+
permissions:
|
|
128
|
+
contents: write # create the release
|
|
129
|
+
steps:
|
|
130
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
131
|
+
with:
|
|
132
|
+
persist-credentials: false
|
|
133
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
134
|
+
with:
|
|
135
|
+
name: dist
|
|
136
|
+
path: dist/
|
|
137
|
+
- name: Create the release
|
|
138
|
+
env:
|
|
139
|
+
GH_TOKEN: ${{ github.token }}
|
|
140
|
+
run: |
|
|
141
|
+
python scripts/release_notes.py "$GITHUB_REF_NAME" > notes.md
|
|
142
|
+
prerelease=""
|
|
143
|
+
case "$GITHUB_REF_NAME" in *a*|*b*|*rc*) prerelease="--prerelease" ;; esac
|
|
144
|
+
gh release create "$GITHUB_REF_NAME" dist/* --verify-tag \
|
|
145
|
+
--repo "$GITHUB_REPOSITORY" --title "vecshift ${GITHUB_REF_NAME#v}" \
|
|
146
|
+
--notes-file notes.md $prerelease
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
|
|
8
|
+
# Virtual environments
|
|
9
|
+
.venv/
|
|
10
|
+
venv/
|
|
11
|
+
|
|
12
|
+
# Tooling caches
|
|
13
|
+
.pytest_cache/
|
|
14
|
+
.mypy_cache/
|
|
15
|
+
.ruff_cache/
|
|
16
|
+
.coverage
|
|
17
|
+
coverage.xml
|
|
18
|
+
htmlcov/
|
|
19
|
+
|
|
20
|
+
# VecShift local state
|
|
21
|
+
.vecshift/
|
|
22
|
+
*.dlq.jsonl
|
|
23
|
+
|
|
24
|
+
# Environment and secrets
|
|
25
|
+
.env
|
|
26
|
+
.env.*
|
|
27
|
+
!.env.example
|
|
28
|
+
|
|
29
|
+
# Editors and OS
|
|
30
|
+
.idea/
|
|
31
|
+
.vscode/
|
|
32
|
+
.DS_Store
|
|
33
|
+
requirements-audit.txt
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
|
|
6
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0]
|
|
11
|
+
|
|
12
|
+
The first release.
|
|
13
|
+
|
|
14
|
+
### Security
|
|
15
|
+
|
|
16
|
+
- API keys are never sent over unencrypted `http://` to remote hosts, redirects are never
|
|
17
|
+
followed, and credentials inside model URLs are rejected. URL query strings are hidden
|
|
18
|
+
wherever a spec is shown.
|
|
19
|
+
- `compat` servers only receive an API key when the spec names one with `key_env=`.
|
|
20
|
+
Previously `VECSHIFT_API_KEY` was sent to any compat URL.
|
|
21
|
+
- The embedding cache is now readable by its owner only (directory `0700`, file `0600`).
|
|
22
|
+
- HTML reports carry a strict Content Security Policy: no network access, and only their
|
|
23
|
+
own script, pinned by hash, may run.
|
|
24
|
+
- SQL generated for `apply` quotes identifiers using the server's keyword list.
|
|
25
|
+
- Crash tracebacks never print local variables, and `--dsn` with a password on the
|
|
26
|
+
command line prints a warning.
|
|
27
|
+
- CI runs `pip-audit`, `zizmor`, and Ruff's security rules on every change and weekly.
|
|
28
|
+
Actions are pinned to commit SHAs, and checkout no longer persists credentials.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- On tables with constant writes, `apply` never finished (a few rows always changed during
|
|
33
|
+
each catch-up pass, so it stopped without building the index) and `cutover` could never
|
|
34
|
+
pass its final check. `apply` now finishes once at most 0.1% of rows (at least 100) are
|
|
35
|
+
left, counting them after the index build; each pass covers only what was pending when it
|
|
36
|
+
began; and `cutover` embeds up to 500 late rows while it holds the write lock.
|
|
37
|
+
- `eval` treated a busy table's few waiting rows as a partial migration and skipped latency.
|
|
38
|
+
- `eval`'s index recall now counts ties: a row exactly as close as the exact 10th result is
|
|
39
|
+
a correct answer. Tables with near-duplicate rows read far too low before.
|
|
40
|
+
- Schema changes wait at most 2 seconds (was 5) for a lock, which bounds how long
|
|
41
|
+
application writes can queue behind them.
|
|
42
|
+
- `apply` explains `could not resize shared memory segment` during the index build
|
|
43
|
+
(Docker's default 64 MB `/dev/shm`), and `plan`'s memory hint mentions `--shm-size`.
|
|
44
|
+
- `plan` described cutover with the wrong column name (`_previous`; it's `_old`).
|
|
45
|
+
|
|
46
|
+
- `plan` (and so `apply`) and `cutover --check` refuse a table whose row-level security binds
|
|
47
|
+
the connecting role, such as one with `FORCE ROW LEVEL SECURITY`. `apply` used to report
|
|
48
|
+
success while skipping the rows the role couldn't see.
|
|
49
|
+
- They also refuse partitioned tables, where `apply` embedded every row and then failed to
|
|
50
|
+
build the index, since PostgreSQL can't build one concurrently on a partitioned table.
|
|
51
|
+
|
|
52
|
+
- `vecshift doctor` sampled only the start of large tables, so rows written later (often by a
|
|
53
|
+
newer model) could be missed. Samples are now spread across the table.
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
|
|
57
|
+
- A Docker image that runs as a non-root user, and an offline demo:
|
|
58
|
+
`cd demo && docker compose up` runs doctor, plan, apply, eval, and cutover on a sample
|
|
59
|
+
table of English and Arabic articles, with no API key. See `docs/demo.md`.
|
|
60
|
+
- Job files accept the built-in `hash/N` models, for trying vecshift out; `plan` warns
|
|
61
|
+
that they're test models (`plan.baseline_model`).
|
|
62
|
+
- `vecshift eval` compares the old and new vectors on your data before cutover, without
|
|
63
|
+
re-embedding documents. It reports recall@1, recall@10, and MRR@10 overall and for each
|
|
64
|
+
query → document script pair (Arabic and Latin), how much the top results changed,
|
|
65
|
+
query embedding latency, and search latency (p50, p95, p99) with index recall across
|
|
66
|
+
`hnsw.ef_search` or `ivfflat.probes` settings. It ends with GO, NO-GO, or INCONCLUSIVE
|
|
67
|
+
(exit status 0, 1, or 3), with optional latency limits. If the old model is unavailable
|
|
68
|
+
it judges the old side from stored vectors, by how well each row's nearest rows stay in
|
|
69
|
+
the same document. Queries come from the rows themselves, a labeled file, or an LLM,
|
|
70
|
+
optionally in the other language (`--cross-language`). It's read-only, and `--dsn-env`
|
|
71
|
+
can point it at a replica.
|
|
72
|
+
- `vecshift eval --html FILE` writes a self-contained report: the verdict, headline numbers
|
|
73
|
+
with their change, recall@10 per language as a before → after chart, and a latency vs
|
|
74
|
+
accuracy chart for both sides, each with a table view, in light and dark themes.
|
|
75
|
+
- `source.model` in the job file names the model that made the current vectors.
|
|
76
|
+
|
|
77
|
+
- `vecshift cutover` switches searches to the new vectors: in one transaction it renames
|
|
78
|
+
the live column to `<name>_old` and the new column to `<name>`, so the application's SQL
|
|
79
|
+
doesn't change. It first embeds rows added or edited since the last `apply`, then
|
|
80
|
+
confirms under a brief write lock that every row has a new vector. `--check` reports
|
|
81
|
+
whether the switch is safe without changing anything: missing vectors, an unbuilt index,
|
|
82
|
+
views bound to the column, and a vector size change the application must match.
|
|
83
|
+
- `vecshift rollback` renames the columns back, and reports rows added or edited since
|
|
84
|
+
cutover that have no old-model vector.
|
|
85
|
+
- `vecshift cleanup` drops the old column, its index, and its trigger once you're sure.
|
|
86
|
+
- `vecshift apply --until PERCENT` stops once that share of rows has a new vector.
|
|
87
|
+
|
|
88
|
+
### Changed
|
|
89
|
+
|
|
90
|
+
- The sync trigger keeps a new vector the application writes itself in the same update,
|
|
91
|
+
and pins its function's `search_path`. Dropping the column it guards by hand is refused
|
|
92
|
+
instead of leaving a broken trigger behind.
|
|
93
|
+
- `plan` reports a table that was already cut over and not cleaned up.
|
|
94
|
+
|
|
95
|
+
- `vecshift apply` runs a migration on pgvector and Supabase: it adds the new column and a
|
|
96
|
+
trigger that clears a row's new vector when its text changes, embeds every row in
|
|
97
|
+
resumable batches, makes catch-up passes for rows edited during the run, then builds the
|
|
98
|
+
index concurrently and checks every vector's size. Writes are guarded so a vector only
|
|
99
|
+
lands if the row's text is unchanged. It stops cleanly on Ctrl-C or before passing
|
|
100
|
+
`limits.budget_usd` (counted across runs), isolates rows the provider rejects, and
|
|
101
|
+
`--json` streams progress as JSON lines. Exit status 3 means "stopped; run again".
|
|
102
|
+
- `plan` now lists the sync trigger among the changes, and reports tables with a
|
|
103
|
+
composite primary key, which `apply` doesn't support yet.
|
|
104
|
+
|
|
105
|
+
- `vecshift init` writes a commented `vecshift.yaml` job file describing a migration: the
|
|
106
|
+
source table, the side-by-side target column, the new model, and limits such as a budget.
|
|
107
|
+
- `vecshift plan` checks a job against the database without changing anything. It shows
|
|
108
|
+
the SQL apply would run, estimates rows, tokens, cost, duration, storage, and index build
|
|
109
|
+
memory, and reports anything that would make the migration fail, exiting with status 1 on
|
|
110
|
+
errors. `--probe` measures the real model's size, token counts, and speed on 16 rows.
|
|
111
|
+
- `vecshift bench` compares embedding models on a sample of your documents, from a JSONL
|
|
112
|
+
file or a pgvector table. It reports recall@1, recall@10, MRR@10, query latency,
|
|
113
|
+
throughput, cost per million documents, and storage, as a terminal table, `--json`, or an
|
|
114
|
+
`--html` leaderboard. Queries come from the documents themselves (free), from an LLM
|
|
115
|
+
(`--generate-queries`), or from your labeled file (`--queries`).
|
|
116
|
+
- Embedding providers: OpenAI, Ollama, any OpenAI-compatible server, and a free hashing
|
|
117
|
+
baseline, with batching, retries that respect rate limits, known query and document
|
|
118
|
+
prefixes, and an on-disk embedding cache. Installed with `pip install 'vecshift[bench]'`.
|
|
119
|
+
- `bench` shows estimated tokens and cost and asks before sending text to a remote API.
|
|
120
|
+
- Project logo, in light and dark versions, with a GitHub social preview image. The HTML
|
|
121
|
+
report shows it in its header and as its browser-tab icon. `scripts/make_logo.py`
|
|
122
|
+
regenerates the logo files.
|
|
123
|
+
- `vecshift doctor --html FILE` writes a self-contained HTML report: a verdict, key numbers,
|
|
124
|
+
filterable findings, and charts of vector lengths, models, and vector sizes. It works
|
|
125
|
+
offline, supports light and dark themes, and contains statistics only.
|
|
126
|
+
- The JSON report gains a `facts` section with the measurements behind the findings.
|
|
127
|
+
|
|
128
|
+
- `vecshift doctor` for pgvector, including Supabase: a read-only check of an existing index
|
|
129
|
+
for missing source text, mixed vector sizes or models, zero and unnormalized vectors,
|
|
130
|
+
duplicates, missing ANN indexes, row-level security gaps, and how live writes could be
|
|
131
|
+
tracked during a migration. Supports `--json` and `--fail-on` for CI.
|
|
132
|
+
- PostgreSQL connections that work with every Supabase mode (direct, session pooler,
|
|
133
|
+
transaction pooler), require TLS for Supabase hosts, and never print passwords.
|
|
134
|
+
- Canonical `Record` type with tombstones and `updated_at` conflict resolution.
|
|
135
|
+
- `EmbeddingFingerprint` and model tags that identify a vector space.
|
|
136
|
+
- `Capability` flags and plugin contracts for sources, targets, and embedding providers.
|
|
137
|
+
- `vecshift fingerprint` and `vecshift --version` commands.
|
|
138
|
+
|
|
139
|
+
[Unreleased]: https://github.com/Osamamu64/vecshift/compare/v0.1.0...HEAD
|
|
140
|
+
[0.1.0]: https://github.com/Osamamu64/vecshift/releases/tag/v0.1.0
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1
|
|
2
|
+
# The vecshift CLI in a small image that runs as a non-root user.
|
|
3
|
+
#
|
|
4
|
+
# docker build -t vecshift .
|
|
5
|
+
# docker run --rm -e VECSHIFT_DSN vecshift doctor --table documents
|
|
6
|
+
#
|
|
7
|
+
# The base image is pinned by digest; Dependabot keeps it current. PYTHON_IMAGE can point
|
|
8
|
+
# at a mirror with the same digest, such as mirror.gcr.io/library/python.
|
|
9
|
+
ARG PYTHON_IMAGE=python:3.13-slim@sha256:70729b46c69b4f1e97c4822c1af3df53a1476cf5ddc6c087c0c10bc3a5678c2f
|
|
10
|
+
|
|
11
|
+
FROM ${PYTHON_IMAGE} AS build
|
|
12
|
+
RUN pip install --no-cache-dir uv==0.11.32
|
|
13
|
+
WORKDIR /src
|
|
14
|
+
COPY pyproject.toml uv.lock README.md LICENSE NOTICE ./
|
|
15
|
+
COPY src ./src
|
|
16
|
+
# Dependencies come from the lock file, checked against its hashes.
|
|
17
|
+
RUN uv export --frozen --no-dev --no-emit-project -o /dist/requirements.txt \
|
|
18
|
+
&& uv build --wheel -o /dist
|
|
19
|
+
|
|
20
|
+
FROM ${PYTHON_IMAGE}
|
|
21
|
+
LABEL org.opencontainers.image.title="vecshift" \
|
|
22
|
+
org.opencontainers.image.description="Zero-downtime embedding model migrations for pgvector and Supabase" \
|
|
23
|
+
org.opencontainers.image.source="https://github.com/Osamamu64/vecshift" \
|
|
24
|
+
org.opencontainers.image.licenses="Apache-2.0"
|
|
25
|
+
ENV PYTHONDONTWRITEBYTECODE=1 \
|
|
26
|
+
PYTHONUNBUFFERED=1 \
|
|
27
|
+
PIP_DISABLE_PIP_VERSION_CHECK=1
|
|
28
|
+
RUN --mount=type=bind,from=build,source=/dist,target=/dist \
|
|
29
|
+
pip install --no-cache-dir --require-hashes -r /dist/requirements.txt \
|
|
30
|
+
&& pip install --no-cache-dir --no-deps /dist/*.whl \
|
|
31
|
+
&& useradd --create-home --uid 1000 --user-group vecshift
|
|
32
|
+
USER vecshift
|
|
33
|
+
WORKDIR /home/vecshift
|
|
34
|
+
ENTRYPOINT ["vecshift"]
|
|
35
|
+
CMD ["--help"]
|