weightsdb 0.2.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.
Files changed (51) hide show
  1. weightsdb-0.2.0/.editorconfig +23 -0
  2. weightsdb-0.2.0/.github/workflows/ci.yml +164 -0
  3. weightsdb-0.2.0/.github/workflows/release.yml +57 -0
  4. weightsdb-0.2.0/.gitignore +100 -0
  5. weightsdb-0.2.0/.importlinter +21 -0
  6. weightsdb-0.2.0/.pre-commit-config.yaml +22 -0
  7. weightsdb-0.2.0/CHANGELOG.md +75 -0
  8. weightsdb-0.2.0/CONTRIBUTING.md +50 -0
  9. weightsdb-0.2.0/LICENSE +201 -0
  10. weightsdb-0.2.0/PKG-INFO +96 -0
  11. weightsdb-0.2.0/README.md +62 -0
  12. weightsdb-0.2.0/SECURITY.md +39 -0
  13. weightsdb-0.2.0/docs/README.md +8 -0
  14. weightsdb-0.2.0/docs/adoption-checklist.md +132 -0
  15. weightsdb-0.2.0/docs/packages/weightsdb/development-plan.md +158 -0
  16. weightsdb-0.2.0/docs/packages/weightsdb/spec.md +265 -0
  17. weightsdb-0.2.0/docs/quickstart.md +146 -0
  18. weightsdb-0.2.0/pyproject.toml +104 -0
  19. weightsdb-0.2.0/requirements/README.md +64 -0
  20. weightsdb-0.2.0/requirements/ci.lock +848 -0
  21. weightsdb-0.2.0/requirements/release.in +6 -0
  22. weightsdb-0.2.0/requirements/release.lock +493 -0
  23. weightsdb-0.2.0/src/weightsdb/__about__.py +1 -0
  24. weightsdb-0.2.0/src/weightsdb/__init__.py +71 -0
  25. weightsdb-0.2.0/src/weightsdb/backup.py +610 -0
  26. weightsdb-0.2.0/src/weightsdb/engine.py +229 -0
  27. weightsdb-0.2.0/src/weightsdb/errors.py +83 -0
  28. weightsdb-0.2.0/src/weightsdb/health.py +318 -0
  29. weightsdb-0.2.0/src/weightsdb/migrations.py +328 -0
  30. weightsdb-0.2.0/src/weightsdb/py.typed +0 -0
  31. weightsdb-0.2.0/src/weightsdb/redaction.py +26 -0
  32. weightsdb-0.2.0/src/weightsdb/session.py +115 -0
  33. weightsdb-0.2.0/src/weightsdb/testing.py +131 -0
  34. weightsdb-0.2.0/src/weightsdb/types.py +187 -0
  35. weightsdb-0.2.0/tests/conftest.py +19 -0
  36. weightsdb-0.2.0/tests/contract/test_public_api.py +107 -0
  37. weightsdb-0.2.0/tests/integration/_migration_fixture/__init__.py +0 -0
  38. weightsdb-0.2.0/tests/integration/_migration_fixture/env.py +44 -0
  39. weightsdb-0.2.0/tests/integration/_migration_fixture/models.py +20 -0
  40. weightsdb-0.2.0/tests/integration/_migration_fixture/versions/0001_create_widgets.py +28 -0
  41. weightsdb-0.2.0/tests/integration/_migration_fixture/versions/0002_add_note.py +25 -0
  42. weightsdb-0.2.0/tests/integration/test_backup_restore.py +625 -0
  43. weightsdb-0.2.0/tests/integration/test_migrations.py +240 -0
  44. weightsdb-0.2.0/tests/integration/test_two_schemas.py +69 -0
  45. weightsdb-0.2.0/tests/performance/test_db_overhead.py +124 -0
  46. weightsdb-0.2.0/tests/unit/test_engine.py +273 -0
  47. weightsdb-0.2.0/tests/unit/test_health.py +331 -0
  48. weightsdb-0.2.0/tests/unit/test_redaction.py +35 -0
  49. weightsdb-0.2.0/tests/unit/test_session.py +98 -0
  50. weightsdb-0.2.0/tests/unit/test_testing.py +47 -0
  51. weightsdb-0.2.0/tests/unit/test_types.py +242 -0
@@ -0,0 +1,23 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ trim_trailing_whitespace = true
8
+ indent_style = space
9
+ indent_size = 4
10
+
11
+ [*.py]
12
+ indent_size = 4
13
+ max_line_length = 100
14
+
15
+ [*.{toml,yml,yaml,json}]
16
+ indent_size = 2
17
+
18
+ [*.md]
19
+ trim_trailing_whitespace = false
20
+ max_line_length = off
21
+
22
+ [Makefile]
23
+ indent_style = tab
@@ -0,0 +1,164 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ format:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-python@v5
14
+ with: { python-version: "3.12" }
15
+ - run: pip install ruff
16
+ - run: ruff format --check .
17
+
18
+ lint:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - uses: actions/setup-python@v5
23
+ with: { python-version: "3.12" }
24
+ - run: pip install ruff
25
+ - run: ruff check .
26
+
27
+ types:
28
+ runs-on: ubuntu-latest
29
+ steps:
30
+ - uses: actions/checkout@v4
31
+ - uses: actions/setup-python@v5
32
+ with: { python-version: "3.12" }
33
+ - run: pip install --require-hashes -r requirements/ci.lock
34
+ - run: pip install . --no-deps
35
+ - run: mypy src tests
36
+
37
+ boundaries:
38
+ runs-on: ubuntu-latest
39
+ steps:
40
+ - uses: actions/checkout@v4
41
+ - uses: actions/setup-python@v5
42
+ with: { python-version: "3.12" }
43
+ - run: pip install --require-hashes -r requirements/ci.lock
44
+ - run: pip install . --no-deps
45
+ - run: lint-imports
46
+
47
+ tests:
48
+ runs-on: ubuntu-latest
49
+ strategy:
50
+ matrix:
51
+ python-version: ["3.12", "3.13"]
52
+ steps:
53
+ - uses: actions/checkout@v4
54
+ - uses: actions/setup-python@v5
55
+ with: { python-version: "${{ matrix.python-version }}" }
56
+ - run: pip install --require-hashes -r requirements/ci.lock
57
+ - run: pip install . --no-deps
58
+ - run: pytest -m "not live and not performance" --cov --cov-report=xml
59
+
60
+ tests-314-early-warning:
61
+ runs-on: ubuntu-latest
62
+ continue-on-error: true
63
+ steps:
64
+ - uses: actions/checkout@v4
65
+ - uses: actions/setup-python@v5
66
+ with: { python-version: "3.14" }
67
+ # Deliberately unpinned: ci.lock is resolved on 3.13, and pinning a version that has no
68
+ # 3.14 wheels would defeat the purpose of an early warning.
69
+ - run: pip install -e ".[dev]"
70
+ - run: pytest -m "not live and not performance"
71
+
72
+ db-matrix:
73
+ name: tests (PostgreSQL)
74
+ runs-on: ubuntu-latest
75
+ services:
76
+ postgres:
77
+ image: postgres:16
78
+ # Credentials and database name match `weightsdb.testing`'s documented default URL, so the
79
+ # service this job starts is the one the package looks for.
80
+ env:
81
+ POSTGRES_USER: weightsdb
82
+ POSTGRES_PASSWORD: weightsdb
83
+ POSTGRES_DB: weightsdb_test
84
+ ports: ["5432:5432"]
85
+ options: >-
86
+ --health-cmd "pg_isready -U weightsdb -d weightsdb_test" --health-interval 5s --health-timeout 5s --health-retries 10
87
+ steps:
88
+ - uses: actions/checkout@v4
89
+ - uses: actions/setup-python@v5
90
+ with: { python-version: "3.12" }
91
+ - run: pip install --require-hashes -r requirements/ci.lock
92
+ - run: pip install . --no-deps
93
+ - run: pytest -m "not live and not performance" tests/integration
94
+ env:
95
+ WEIGHTSDB_REQUIRE_POSTGRES: "1"
96
+ # `temporary_postgres` reads WEIGHTSDB_POSTGRES_URL, not DATABASE_URL. Set explicitly
97
+ # rather than relying on the default, so the job states which server it is testing.
98
+ WEIGHTSDB_POSTGRES_URL: postgresql+psycopg://weightsdb:weightsdb@localhost:5432/weightsdb_test
99
+
100
+ coverage:
101
+ needs: [tests]
102
+ runs-on: ubuntu-latest
103
+ steps:
104
+ - uses: actions/checkout@v4
105
+ - uses: actions/setup-python@v5
106
+ with: { python-version: "3.12" }
107
+ - run: pip install --require-hashes -r requirements/ci.lock
108
+ - run: pip install . --no-deps
109
+ - run: pytest -m "not live and not performance" --cov --cov-report=term-missing --cov-fail-under=95
110
+
111
+ contracts:
112
+ runs-on: ubuntu-latest
113
+ steps:
114
+ - uses: actions/checkout@v4
115
+ - uses: actions/setup-python@v5
116
+ with: { python-version: "3.12" }
117
+ - run: pip install --require-hashes -r requirements/ci.lock
118
+ - run: pip install . --no-deps
119
+ - run: pytest -m contract
120
+
121
+ security:
122
+ runs-on: ubuntu-latest
123
+ steps:
124
+ - uses: actions/checkout@v4
125
+ # gitleaks scans *history*, and `actions/checkout` fetches a single commit by default.
126
+ # For a push it is handed `<first-pushed>^..<last-pushed>`, so the parent of the first
127
+ # pushed commit has to be in the object store; in a depth-1 clone it is not, and git
128
+ # answers "unknown revision", which the action reports as exit code 1. It fails the same
129
+ # way whether or not a secret exists, so a green run would not have meant anything either.
130
+ with: { fetch-depth: 0 }
131
+ - uses: actions/setup-python@v5
132
+ with: { python-version: "3.12" }
133
+ - run: pip install pip-audit
134
+ # Audit the locked sets, not the job's own environment: a bare `pip-audit` here would
135
+ # inspect an environment containing only pip-audit itself (Security Standards §11).
136
+ - run: pip-audit --require-hashes -r requirements/ci.lock
137
+ - run: pip-audit --require-hashes -r requirements/release.lock
138
+ - uses: gitleaks/gitleaks-action@v2
139
+ env:
140
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
141
+
142
+ build:
143
+ runs-on: ubuntu-latest
144
+ steps:
145
+ - uses: actions/checkout@v4
146
+ - uses: actions/setup-python@v5
147
+ with: { python-version: "3.12" }
148
+ - run: pip install --require-hashes -r requirements/release.lock
149
+ - run: python -m build --no-isolation
150
+ - run: twine check dist/*
151
+ - uses: actions/upload-artifact@v4
152
+ with: { name: dist, path: dist/ }
153
+
154
+ install-check:
155
+ needs: [build]
156
+ runs-on: ubuntu-latest
157
+ steps:
158
+ - uses: actions/checkout@v4
159
+ - uses: actions/setup-python@v5
160
+ with: { python-version: "3.12" }
161
+ - uses: actions/download-artifact@v4
162
+ with: { name: dist, path: dist/ }
163
+ - run: pip install dist/*.whl
164
+ - run: python -c "import weightsdb"
@@ -0,0 +1,57 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*.*.*"]
6
+ workflow_dispatch: # manual TestPyPI dry run; see publish-testpypi below
7
+
8
+ permissions:
9
+ id-token: write # required for PyPI Trusted Publishing
10
+ contents: write # required to create the GitHub release
11
+
12
+ jobs:
13
+ release:
14
+ # Tag pushes only. Without this, clicking "Run workflow" for the TestPyPI dry run below would
15
+ # also fire this job and publish to real PyPI — the exact opposite of a dry run.
16
+ if: github.event_name == 'push'
17
+ runs-on: ubuntu-latest
18
+ environment: pypi # must match the Environment name set on the PyPI trusted publisher
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - uses: actions/setup-python@v5
22
+ with: { python-version: "3.12" }
23
+ # Byte-for-byte the dry run's build chain below. A real release that resolved `build` and
24
+ # `hatchling` fresh from PyPI would not be the artifact the dry run proved.
25
+ - run: pip install --require-hashes -r requirements/release.lock
26
+ - run: python -m build --no-isolation
27
+ - run: twine check dist/*
28
+ - run: pip install "dist/$(ls dist | grep .whl)[dev]"
29
+ - run: pytest -m "not live and not performance"
30
+ - name: Publish to PyPI
31
+ uses: pypa/gh-action-pypi-publish@release/v1
32
+ - name: Create GitHub release
33
+ uses: softprops/action-gh-release@v2
34
+ with:
35
+ generate_release_notes: true
36
+ files: dist/*
37
+
38
+ publish-testpypi:
39
+ # Manual only, via Actions -> Release -> Run workflow. Packaging and Release Standards §6
40
+ # requires a successful TestPyPI publish ahead of a package's first real release; 0.2.0 is
41
+ # this package's first published version, so run this once before tagging v0.2.0. Later
42
+ # releases may skip it, or use it again as a dry run.
43
+ if: github.event_name == 'workflow_dispatch'
44
+ runs-on: ubuntu-latest
45
+ steps:
46
+ - uses: actions/checkout@v4
47
+ - uses: actions/setup-python@v5
48
+ with: { python-version: "3.12" }
49
+ - run: pip install --require-hashes -r requirements/release.lock
50
+ - run: python -m build --no-isolation
51
+ - run: twine check dist/*
52
+ - run: pip install "dist/$(ls dist | grep .whl)[dev]"
53
+ - run: pytest -m "not live and not performance"
54
+ - name: Publish to TestPyPI
55
+ uses: pypa/gh-action-pypi-publish@release/v1
56
+ with:
57
+ repository-url: https://test.pypi.org/legacy/
@@ -0,0 +1,100 @@
1
+ # ---- Python ----
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+ MANIFEST
23
+
24
+ # ---- Packaging / build backends ----
25
+ pip-wheel-metadata/
26
+ share/python-wheels/
27
+
28
+ # ---- Testing / coverage ----
29
+ .pytest_cache/
30
+ .cache
31
+ .coverage
32
+ .coverage.*
33
+ coverage.xml
34
+ *.cover
35
+ *.py,cover
36
+ htmlcov/
37
+ nosetests.xml
38
+ .hypothesis/
39
+
40
+ # ---- Type checking / linting ----
41
+ .mypy_cache/
42
+ .dmypy.json
43
+ dmypy.json
44
+ .ruff_cache/
45
+ .pytype/
46
+
47
+ # ---- Virtual environments ----
48
+ .venv/
49
+ venv/
50
+ ENV/
51
+ env/
52
+ env.bak/
53
+ venv.bak/
54
+ .python-version
55
+
56
+ # ---- Distribution / dependency locking ----
57
+ # requirements/*.lock is deliberately NOT ignored. The lock files are committed
58
+ # inputs: CI and release jobs install from them with --require-hashes (Packaging
59
+ # and Release Standards §4, Security Standards §11), so an ignored lock file
60
+ # means a checkout that cannot build.
61
+
62
+ # ---- Editors / OS ----
63
+ .vscode/
64
+ .idea/
65
+ *.swp
66
+ *.swo
67
+ .DS_Store
68
+ Thumbs.db
69
+
70
+ # ---- Suite runtime state (this component's local data) ----
71
+ # The application writes to XDG paths at runtime (~/.config, ~/.local/share,
72
+ # ~/.local/state per Master Architecture §1.2), never inside the repository.
73
+ # These entries only guard against a developer pointing XDG_* at the repo
74
+ # during local testing.
75
+ .local/
76
+ .config/
77
+ *.sqlite3
78
+ *.sqlite3-journal
79
+ *.sqlite3-wal
80
+ *.sqlite3-shm
81
+ /data/
82
+ /logs/
83
+ /backups/
84
+ /artifacts/
85
+ /exports/
86
+
87
+ # ---- Secrets ----
88
+ # Per Security Standards §8: secrets are never committed. Config files may
89
+ # only name where a secret comes from (*_env / *_file), never the value.
90
+ .env
91
+ .env.*
92
+ *.key
93
+ *.pem
94
+ secrets.toml
95
+
96
+ # ---- Node-free JS assets (MirrorWall consumers may still use a local tool) ----
97
+ node_modules/
98
+
99
+ # ---- Build artifacts from docs generation ----
100
+ docs/api/openapi-v1.json.tmp
@@ -0,0 +1,21 @@
1
+ [importlinter]
2
+ root_package = weightsdb
3
+ include_external_packages = True
4
+
5
+ [importlinter:contract:no-application-imports]
6
+ name = WeightsDB must not import applications
7
+ type = forbidden
8
+ source_modules = weightsdb
9
+ forbidden_modules =
10
+ freeweight
11
+ loadcoach
12
+ ideapress
13
+
14
+ [importlinter:contract:no-sibling-packages]
15
+ name = WeightsDB must not import sibling capability packages
16
+ type = forbidden
17
+ source_modules = weightsdb
18
+ forbidden_modules =
19
+ modelrack
20
+ sweatmeter
21
+ mirrorwall
@@ -0,0 +1,22 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.6.9
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+ - repo: https://github.com/pre-commit/pre-commit-hooks
9
+ rev: v4.6.0
10
+ hooks:
11
+ - id: trailing-whitespace
12
+ - id: end-of-file-fixer
13
+ - id: check-toml
14
+ - id: check-json
15
+ - id: check-added-large-files
16
+ - id: check-merge-conflict
17
+ - id: mixed-line-ending
18
+ args: [--fix=lf]
19
+ - repo: https://github.com/gitleaks/gitleaks
20
+ rev: v8.18.4
21
+ hooks:
22
+ - id: gitleaks
@@ -0,0 +1,75 @@
1
+ # Changelog
2
+
3
+ All notable changes to `weightsdb` are documented here.
4
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows
5
+ [Semantic Versioning](https://semver.org/), pre-1.0 per
6
+ packaging and release standards §3.
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Fixed
11
+ - **Coverage fell below the 95 % floor on Python 3.12 (94.89 %).** The uncovered lines were real
12
+ gaps, not measurement noise: `upsert`'s documented refusal of a third dialect, the `StorageBusy`
13
+ translation on a *read-only* `BEGIN`, `temporary_postgres`'s `WEIGHTSDB_REQUIRE_POSTGRES=1`
14
+ failure path, longest-prefix mount selection against an unsorted mount table, and two backups
15
+ sharing an mtime. Each now has a test; the floor is met on 3.12, 3.13 and 3.14 with margin.
16
+
17
+ ## [0.2.0] — 2026-08-29
18
+
19
+ ### Added
20
+ - Repository scaffold generated from the suite's development plan (no functional code yet).
21
+ - Phase 1: `create_engine_for`, `session_factory`/`session_scope`/`transaction`, `UtcDateTime`,
22
+ `PortableJSON`, `ulid_primary_key`, `measurement_columns`, `upsert`, the `DatabaseError` hierarchy,
23
+ `redact_url`, and `weightsdb.testing.temporary_sqlite`/`temporary_postgres` — extracted from
24
+ FreeWeight's `infrastructure.db` (ADR-0011).
25
+ - Phase 2: `MigrationRunner` (upgrade/downgrade/stamp/check_parity with automatic pre-migration
26
+ backup and SQLite restore-on-failure), `backup`/`restore`/`integrity_check` and friends, and
27
+ `weightsdb.testing.migration_harness`.
28
+ - Phase 3: `database_health()` (dialect/version, revision vs head, journal mode, size, free space,
29
+ last backup age, integrity, network-filesystem warning, one `status`/`degraded_reasons` verdict),
30
+ `is_network_filesystem()`, the `py.typed` marker, `docs/quickstart.md` and
31
+ `docs/adoption-checklist.md` for FreeWeight Phase 12.
32
+
33
+ ### Fixed
34
+ - SQLite lock contention beyond `busy_timeout` now raises the typed `StorageBusy` instead of a raw
35
+ `sqlalchemy.exc.OperationalError` — a gap in the code this package was extracted from, invisible
36
+ with one consumer and no test for it.
37
+ - **`psycopg[binary]` is now part of the `dev` extra.** Seventeen tests assert dialect-specific
38
+ behaviour by building a `postgresql://` engine — no server involved — which SQLAlchemy cannot do
39
+ without an importable DBAPI. Every environment that installed `[dev]` alone failed them with
40
+ `ModuleNotFoundError` and landed coverage at 88 % against a 95 % floor; the local `.venv` hid it
41
+ by having been created with `[dev,postgres]`. The optional `postgres` extra is unchanged, and is
42
+ still what a consumer installs to talk to a real server.
43
+ - **The `db-matrix` CI job read the wrong environment variable.** It set `DATABASE_URL`, which
44
+ nothing in this package reads; `weightsdb.testing.temporary_postgres` reads
45
+ `WEIGHTSDB_POSTGRES_URL` and otherwise falls back to its own default. With
46
+ `WEIGHTSDB_REQUIRE_POSTGRES=1` the fallback is a hard failure rather than a skip, so the job had
47
+ never exercised PostgreSQL at all. The service container's user, password and database now match
48
+ the documented default URL, and the job sets `WEIGHTSDB_POSTGRES_URL` explicitly rather than
49
+ relying on that default.
50
+ - **The PostgreSQL round-trip no longer fails on client/server version skew.** `pg_restore` exits 1
51
+ for warnings unrelated to the data — a client newer than the server emits
52
+ `SET transaction_timeout = 0`, which an older server rejects and reports as "errors ignored on
53
+ restore" — so the test asserted an exit code where its actual claim is that the rows come back.
54
+ It now checks the restored rows and attaches `pg_restore`'s stderr when they are wrong. A missing
55
+ `pg_dump` skips the test the way a missing `pg_restore` already did, instead of failing inside
56
+ `backup()`.
57
+ - **`test_restore_reports_a_backup_it_cannot_open` skips when running as root.** `chmod 000` does
58
+ not make a file unreadable to root, so the open succeeds and the corruption branch answers
59
+ instead — a failure that says nothing about the code. CI runs as an ordinary user; a container
60
+ run as root no longer reports it as a defect.
61
+ - The `dev` extra moves to `pytest>=9.0.3,<10`, matching BaseAiCore, SetSpec, ModelRack and
62
+ SweatMeter: PYSEC-2026-1845 affects pytest through 9.0.2 and failed the security job.
63
+ - **The backup tests' table seed was not dialect-portable.** `INSERT INTO t (name) …` relies on
64
+ SQLite treating `INTEGER PRIMARY KEY` as a rowid alias; PostgreSQL rejects the row with a
65
+ NOT NULL violation. The id is now supplied explicitly, so the round-trip test can actually run
66
+ on the dialect it names.
67
+
68
+ ### Changed
69
+ - CI installs from committed, hash-verified lockfiles (`requirements/ci.lock`,
70
+ `requirements/release.lock`) rather than an editable checkout, per Packaging Standards §4;
71
+ `pip-audit` audits those locks instead of an empty environment; `release.yml` gains the
72
+ `pypi` deployment environment, the manual TestPyPI dry run required before a first release, and
73
+ a build chain pinned byte-for-byte to the one the dry run proves. `[tool.coverage.run] source`
74
+ now names the importable package rather than `src/weightsdb`, because a non-editable install
75
+ reports 0 % against a path-based source.
@@ -0,0 +1,50 @@
1
+ # Contributing to WeightsDB
2
+
3
+ This repository is one component of the Local AI Suite. Before changing anything, read
4
+ `docs/packages/weightsdb/spec.md` and the current
5
+ phase in `development-plan.md` — both are in this repository's `docs/` folder, copied from the suite's
6
+ central documentation set so this repository can be worked on independently.
7
+
8
+ ## Development setup
9
+
10
+ ```bash
11
+ python -m venv .venv
12
+ source .venv/bin/activate
13
+ pip install -e ".[dev]"
14
+ pre-commit install
15
+ ```
16
+
17
+ ## Required reading, in order
18
+
19
+ 1. This component's spec — purpose, scope, non-goals, contracts.
20
+ 2. `development-plan.md` in the same folder — the phase you are implementing, its acceptance criteria and its tests.
21
+
22
+ ## Rules that apply to every change here
23
+
24
+ * Follow the architecture's dependency direction.
25
+ This repository's `.importlinter` enforces it in CI; do not weaken that file to make an import work.
26
+ * No business logic in a route handler or CLI command body — both call one service method and render
27
+ .
28
+ * An unavailable measurement is `Unsupported`, never zero, never `None` used as a substitute
29
+ .
30
+ * Prompts are versioned JSON records, not Python string literals.
31
+ * Every phase's acceptance criteria in `development-plan.md` must be demonstrable, not merely
32
+ test-covered — the plan states what to run and what a person should see.
33
+
34
+ ## Before opening a pull request
35
+
36
+ ```bash
37
+ ruff format --check .
38
+ ruff check .
39
+ mypy src tests
40
+ lint-imports
41
+ pytest -m "not live and not performance"
42
+ ```
43
+
44
+ All of the above run in CI (`.github/workflows/ci.yml`); a red CI run blocks merge.
45
+
46
+ ## Commit style
47
+
48
+ Conventional Commits (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`, `perf:`, `build:`,
49
+ `ci:`), with `!` or a `BREAKING CHANGE:` footer for breaking changes. Update `CHANGELOG.md` under
50
+ `## [Unreleased]` for any user-visible change.