commissioner 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.
- commissioner-0.1.0/.editorconfig +23 -0
- commissioner-0.1.0/.github/workflows/ci.yml +167 -0
- commissioner-0.1.0/.github/workflows/release.yml +57 -0
- commissioner-0.1.0/.gitignore +104 -0
- commissioner-0.1.0/.importlinter +42 -0
- commissioner-0.1.0/.pre-commit-config.yaml +22 -0
- commissioner-0.1.0/CHANGELOG.md +46 -0
- commissioner-0.1.0/CONTRIBUTING.md +53 -0
- commissioner-0.1.0/LICENSE +201 -0
- commissioner-0.1.0/PKG-INFO +196 -0
- commissioner-0.1.0/README.md +161 -0
- commissioner-0.1.0/SECURITY.md +46 -0
- commissioner-0.1.0/acceptance/read_verdict.py +63 -0
- commissioner-0.1.0/acceptance/record_and_export.py +138 -0
- commissioner-0.1.0/docs/README.md +10 -0
- commissioner-0.1.0/docs/packages/commissioner/development-plan.md +91 -0
- commissioner-0.1.0/docs/packages/commissioner/spec.md +272 -0
- commissioner-0.1.0/pyproject.toml +116 -0
- commissioner-0.1.0/requirements/README.md +52 -0
- commissioner-0.1.0/requirements/ci.lock +1105 -0
- commissioner-0.1.0/requirements/release.in +6 -0
- commissioner-0.1.0/requirements/release.lock +493 -0
- commissioner-0.1.0/src/commissioner/__about__.py +1 -0
- commissioner-0.1.0/src/commissioner/__init__.py +63 -0
- commissioner-0.1.0/src/commissioner/errors.py +64 -0
- commissioner-0.1.0/src/commissioner/ledger.py +155 -0
- commissioner-0.1.0/src/commissioner/policy.py +119 -0
- commissioner-0.1.0/src/commissioner/py.typed +0 -0
- commissioner-0.1.0/src/commissioner/sql.py +486 -0
- commissioner-0.1.0/src/commissioner/types.py +295 -0
- commissioner-0.1.0/tests/conftest.py +157 -0
- commissioner-0.1.0/tests/integration/egress_subprocess.py +76 -0
- commissioner-0.1.0/tests/integration/hostapp/__init__.py +10 -0
- commissioner-0.1.0/tests/integration/hostapp/migrations/env.py +47 -0
- commissioner-0.1.0/tests/integration/hostapp/migrations/script.py.mako +28 -0
- commissioner-0.1.0/tests/integration/hostapp/migrations/versions/.gitkeep +0 -0
- commissioner-0.1.0/tests/integration/hostapp/models.py +33 -0
- commissioner-0.1.0/tests/integration/test_concurrency.py +77 -0
- commissioner-0.1.0/tests/integration/test_hostapp.py +275 -0
- commissioner-0.1.0/tests/integration/test_mounting.py +211 -0
- commissioner-0.1.0/tests/integration/test_sql_ledger.py +274 -0
- commissioner-0.1.0/tests/performance/test_sql_scaling.py +96 -0
- commissioner-0.1.0/tests/unit/test_decision.py +208 -0
- commissioner-0.1.0/tests/unit/test_ledger_memory.py +143 -0
- commissioner-0.1.0/tests/unit/test_ledger_surface.py +71 -0
- commissioner-0.1.0/tests/unit/test_payload_roundtrip.py +80 -0
- commissioner-0.1.0/tests/unit/test_policy_matrix.py +160 -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,167 @@
|
|
|
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 `DEFAULT_POSTGRES_URL` in tests/conftest.py, so the
|
|
79
|
+
# service this job starts is the one the integration tests look for.
|
|
80
|
+
env:
|
|
81
|
+
POSTGRES_USER: commissioner
|
|
82
|
+
POSTGRES_PASSWORD: commissioner
|
|
83
|
+
POSTGRES_DB: commissioner_test
|
|
84
|
+
ports: ["5432:5432"]
|
|
85
|
+
options: >-
|
|
86
|
+
--health-cmd "pg_isready -U commissioner -d commissioner_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
|
+
# Without this the PostgreSQL legs would *skip* here exactly as they do on a developer's
|
|
96
|
+
# machine, and the job would go green having tested one dialect twice. A skipped dialect
|
|
97
|
+
# is an untested dialect.
|
|
98
|
+
COMMISSIONER_REQUIRE_POSTGRES: "1"
|
|
99
|
+
# The tests read COMMISSIONER_POSTGRES_URL, not DATABASE_URL. Set explicitly rather than
|
|
100
|
+
# relying on the default, so the job states which server it is testing.
|
|
101
|
+
COMMISSIONER_POSTGRES_URL: postgresql+psycopg://commissioner:commissioner@localhost:5432/commissioner_test
|
|
102
|
+
|
|
103
|
+
coverage:
|
|
104
|
+
needs: [tests]
|
|
105
|
+
runs-on: ubuntu-latest
|
|
106
|
+
steps:
|
|
107
|
+
- uses: actions/checkout@v4
|
|
108
|
+
- uses: actions/setup-python@v5
|
|
109
|
+
with: { python-version: "3.12" }
|
|
110
|
+
- run: pip install --require-hashes -r requirements/ci.lock
|
|
111
|
+
- run: pip install . --no-deps
|
|
112
|
+
- run: pytest -m "not live and not performance" --cov --cov-report=term-missing --cov-fail-under=95
|
|
113
|
+
|
|
114
|
+
contracts:
|
|
115
|
+
runs-on: ubuntu-latest
|
|
116
|
+
steps:
|
|
117
|
+
- uses: actions/checkout@v4
|
|
118
|
+
- uses: actions/setup-python@v5
|
|
119
|
+
with: { python-version: "3.12" }
|
|
120
|
+
- run: pip install --require-hashes -r requirements/ci.lock
|
|
121
|
+
- run: pip install . --no-deps
|
|
122
|
+
- run: pytest -m contract
|
|
123
|
+
|
|
124
|
+
security:
|
|
125
|
+
runs-on: ubuntu-latest
|
|
126
|
+
steps:
|
|
127
|
+
- uses: actions/checkout@v4
|
|
128
|
+
# gitleaks scans *history*, and `actions/checkout` fetches a single commit by default.
|
|
129
|
+
# For a push it is handed `<first-pushed>^..<last-pushed>`, so the parent of the first
|
|
130
|
+
# pushed commit has to be in the object store; in a depth-1 clone it is not, and git
|
|
131
|
+
# answers "unknown revision", which the action reports as exit code 1. It fails the same
|
|
132
|
+
# way whether or not a secret exists, so a green run would not have meant anything either.
|
|
133
|
+
with: { fetch-depth: 0 }
|
|
134
|
+
- uses: actions/setup-python@v5
|
|
135
|
+
with: { python-version: "3.12" }
|
|
136
|
+
- run: pip install pip-audit
|
|
137
|
+
# Audit the locked sets, not the job's own environment: a bare `pip-audit` here would
|
|
138
|
+
# inspect an environment containing only pip-audit itself (Security Standards §11).
|
|
139
|
+
- run: pip-audit --require-hashes -r requirements/ci.lock
|
|
140
|
+
- run: pip-audit --require-hashes -r requirements/release.lock
|
|
141
|
+
- uses: gitleaks/gitleaks-action@v2
|
|
142
|
+
env:
|
|
143
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
144
|
+
|
|
145
|
+
build:
|
|
146
|
+
runs-on: ubuntu-latest
|
|
147
|
+
steps:
|
|
148
|
+
- uses: actions/checkout@v4
|
|
149
|
+
- uses: actions/setup-python@v5
|
|
150
|
+
with: { python-version: "3.12" }
|
|
151
|
+
- run: pip install --require-hashes -r requirements/release.lock
|
|
152
|
+
- run: python -m build --no-isolation
|
|
153
|
+
- run: twine check dist/*
|
|
154
|
+
- uses: actions/upload-artifact@v4
|
|
155
|
+
with: { name: dist, path: dist/ }
|
|
156
|
+
|
|
157
|
+
install-check:
|
|
158
|
+
needs: [build]
|
|
159
|
+
runs-on: ubuntu-latest
|
|
160
|
+
steps:
|
|
161
|
+
- uses: actions/checkout@v4
|
|
162
|
+
- uses: actions/setup-python@v5
|
|
163
|
+
with: { python-version: "3.12" }
|
|
164
|
+
- uses: actions/download-artifact@v4
|
|
165
|
+
with: { name: dist, path: dist/ }
|
|
166
|
+
- run: pip install dist/*.whl
|
|
167
|
+
- run: python -c "import commissioner"
|
|
@@ -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.1.0 is
|
|
41
|
+
# this package's first published version, so run this once before tagging v0.1.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,104 @@
|
|
|
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
|
|
101
|
+
|
|
102
|
+
# ---- import-linter cache ----
|
|
103
|
+
# `lint-imports` writes this on every gate run; it is a cache, never an input.
|
|
104
|
+
.import_linter_cache/
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[importlinter]
|
|
2
|
+
root_package = commissioner
|
|
3
|
+
include_external_packages = True
|
|
4
|
+
|
|
5
|
+
[importlinter:contract:no-application-imports]
|
|
6
|
+
name = Commissioner must not import applications
|
|
7
|
+
type = forbidden
|
|
8
|
+
source_modules = commissioner
|
|
9
|
+
forbidden_modules =
|
|
10
|
+
freeweight
|
|
11
|
+
loadcoach
|
|
12
|
+
ideapress
|
|
13
|
+
promptcadence
|
|
14
|
+
|
|
15
|
+
[importlinter:contract:no-sibling-packages]
|
|
16
|
+
name = Commissioner must not import sibling capability packages
|
|
17
|
+
type = forbidden
|
|
18
|
+
source_modules = commissioner
|
|
19
|
+
forbidden_modules =
|
|
20
|
+
modelrack
|
|
21
|
+
sweatmeter
|
|
22
|
+
weightsdb
|
|
23
|
+
mirrorwall
|
|
24
|
+
cutctx
|
|
25
|
+
toolyard
|
|
26
|
+
loadledger
|
|
27
|
+
|
|
28
|
+
[importlinter:contract:only-the-sql-module-imports-sqlalchemy]
|
|
29
|
+
# ADR-0050 §4: `sqlalchemy` is an optional extra so the pure-value core stays installable with
|
|
30
|
+
# nothing but baseaicore and setspec. `alembic` has no exemption at all — the host owns every
|
|
31
|
+
# migration (ADR-0050 decision 5), so nothing in this package imports it, `commissioner.sql`
|
|
32
|
+
# included; it is a `[dev]` dependency for the miniature host only. With
|
|
33
|
+
# `include_external_packages = True`, import-linter collapses every external import to the
|
|
34
|
+
# top-level package, so `commissioner.sql -> sqlalchemy.**` would match nothing and is not written.
|
|
35
|
+
name = Only commissioner.sql may import SQLAlchemy, and nothing may import Alembic
|
|
36
|
+
type = forbidden
|
|
37
|
+
source_modules = commissioner
|
|
38
|
+
forbidden_modules =
|
|
39
|
+
sqlalchemy
|
|
40
|
+
alembic
|
|
41
|
+
ignore_imports =
|
|
42
|
+
commissioner.sql -> sqlalchemy
|
|
@@ -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,46 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `commissioner` 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
|
+
## [0.1.0] — 2026-09-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Repository scaffold: toolchain copied from `py/LoadLedger` (hatchling, ruff, mypy strict,
|
|
14
|
+
import-linter, pytest with `pytest-randomly`, hash-pinned `requirements/` locks, CI and release
|
|
15
|
+
workflows), with the `setspec` dependency and its import-linter allowance added (this package,
|
|
16
|
+
like MirrorWall, is permitted to import SetSpec — master architecture §2).
|
|
17
|
+
- Phase 1, the pure core: `EgressTarget`, `EgressRequest`, `Verdict`, `EgressDecision` with
|
|
18
|
+
`to_payload`/`from_payload` against `setspec.governance.v1`'s `governance.egress_decision` 1.0;
|
|
19
|
+
the `EgressPolicy` protocol and `OrderedClassificationPolicy`, the whole shipped policy in four
|
|
20
|
+
rows (ADR-0054 rule 2); `CommissionerError`/`StoreFailure`. No I/O, no SQL, no logging, no
|
|
21
|
+
environment reads.
|
|
22
|
+
- Phase 2, the durable half: `commissioner.ledger` and `commissioner.sql` under the new
|
|
23
|
+
`commissioner[sql]` extra (ADR-0050, LoadLedger's mounting pattern copied by hand — the roadmap's
|
|
24
|
+
revisit trigger is a third mountable package).
|
|
25
|
+
- `EgressLedger` protocol and `InMemoryEgressLedger` — a process-local, thread-safe ledger with
|
|
26
|
+
no store of its own.
|
|
27
|
+
- `mount_egress_tables(metadata, *, prefix="egress_") -> EgressTables` adds one table,
|
|
28
|
+
`{prefix}decisions`, to a host's own `MetaData`: `decision_id` (primary key), `run_id`,
|
|
29
|
+
`verdict`, `target_name` and `decided_at` as indexed, filterable columns, and
|
|
30
|
+
`decision_json` — the decision's own `governance.egress_decision` payload, in canonical form —
|
|
31
|
+
as the byte-stable record. `verdict` is stored as plain text, never `sa.Enum`, so the same
|
|
32
|
+
column type renders on both dialects.
|
|
33
|
+
- `SqlEgressLedger(session_factory, *, table_prefix="egress_")` — `record`/`decisions` over a
|
|
34
|
+
host-owned session factory; no engine, no URL, no migration. Refuses a dialect other than
|
|
35
|
+
SQLite or PostgreSQL with `UnsupportedDialect`, and a `decision_id` this ledger has already
|
|
36
|
+
recorded with `StoreFailure`, rather than silently absorbing either.
|
|
37
|
+
- `UnsupportedDialect` (`EGRESS_UNSUPPORTED_DIALECT`), added to the error hierarchy alongside
|
|
38
|
+
`StoreFailure`.
|
|
39
|
+
- The append-only surface is asserted structurally, not merely documented: neither
|
|
40
|
+
`commissioner.ledger` nor `commissioner.sql` exposes an update or delete path, over both the
|
|
41
|
+
protocol and both implementations.
|
|
42
|
+
- `.importlinter`'s `no-sql-in-phase-1` contract replaced by
|
|
43
|
+
`only-the-sql-module-imports-sqlalchemy`: `sqlalchemy` is now permitted in `commissioner.sql`
|
|
44
|
+
alone, and nothing in the package imports `alembic` at runtime (the host owns every migration).
|
|
45
|
+
- The `[sql]` extra: `sqlalchemy>=2,<3`. The pure core still resolves to `baseaicore` and `setspec`
|
|
46
|
+
alone.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Contributing to Commissioner
|
|
2
|
+
|
|
3
|
+
This repository is one component of the Local AI Suite. Before changing anything, read
|
|
4
|
+
`docs/packages/commissioner/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
|
+
3. [ADR-0054](docs/packages/commissioner/../../adr/0054-commissioner-records-egress-it-does-not-enforce-it.md) — the boundary that keeps this package small: the payload, the ordered comparison, and an append-only ledger, nothing else.
|
|
22
|
+
|
|
23
|
+
## Rules that apply to every change here
|
|
24
|
+
|
|
25
|
+
* Follow the architecture's dependency direction.
|
|
26
|
+
This repository's `.importlinter` enforces it in CI; do not weaken that file to make an import work.
|
|
27
|
+
* No business logic in a route handler or CLI command body — both call one service method and render
|
|
28
|
+
.
|
|
29
|
+
* An unavailable measurement is `Unsupported`, never zero, never `None` used as a substitute
|
|
30
|
+
.
|
|
31
|
+
* Prompts are versioned JSON records, not Python string literals.
|
|
32
|
+
* Every phase's acceptance criteria in `development-plan.md` must be demonstrable, not merely
|
|
33
|
+
test-covered — the plan states what to run and what a person should see.
|
|
34
|
+
* **No enforcement.** `evaluate` never raises for a deny, and nothing here makes an HTTP call or
|
|
35
|
+
halts a caller. If a change starts to look like enforcement, it belongs in the caller.
|
|
36
|
+
|
|
37
|
+
## Before opening a pull request
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
ruff format --check .
|
|
41
|
+
ruff check .
|
|
42
|
+
mypy src tests
|
|
43
|
+
lint-imports
|
|
44
|
+
pytest -m "not live and not performance"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
All of the above run in CI (`.github/workflows/ci.yml`); a red CI run blocks merge.
|
|
48
|
+
|
|
49
|
+
## Commit style
|
|
50
|
+
|
|
51
|
+
Conventional Commits (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`, `perf:`, `build:`,
|
|
52
|
+
`ci:`), with `!` or a `BREAKING CHANGE:` footer for breaking changes. Update `CHANGELOG.md` under
|
|
53
|
+
`## [Unreleased]` for any user-visible change.
|