implicant 0.2.0__tar.gz → 0.6.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 (75) hide show
  1. implicant-0.6.0/.github/workflows/publish-pypi.yml +87 -0
  2. implicant-0.6.0/.github/workflows/release.yml +66 -0
  3. {implicant-0.2.0 → implicant-0.6.0}/.gitignore +10 -0
  4. implicant-0.6.0/PKG-INFO +229 -0
  5. implicant-0.6.0/README.md +210 -0
  6. implicant-0.6.0/pyproject.toml +79 -0
  7. {implicant-0.2.0 → implicant-0.6.0}/src/implicant/__init__.py +15 -1
  8. implicant-0.6.0/src/implicant/binarize.py +614 -0
  9. implicant-0.6.0/src/implicant/cli/main.py +358 -0
  10. {implicant-0.2.0 → implicant-0.6.0}/src/implicant/client.py +97 -20
  11. {implicant-0.2.0 → implicant-0.6.0}/src/implicant/config.py +13 -0
  12. implicant-0.6.0/src/implicant/demo_keys/README.txt +8 -0
  13. implicant-0.6.0/src/implicant/demo_keys/bgv-n16384-L7-a394f2ee7032/public_key.bin +0 -0
  14. implicant-0.6.0/src/implicant/demo_keys/bgv-n16384-L7-a394f2ee7032/secret_key.bin +0 -0
  15. implicant-0.6.0/src/implicant/demo_keys/bgv-n16384-L8-5f657f27f77c/public_key.bin +0 -0
  16. implicant-0.6.0/src/implicant/demo_keys/bgv-n16384-L8-5f657f27f77c/secret_key.bin +0 -0
  17. implicant-0.6.0/src/implicant/keys.py +376 -0
  18. {implicant-0.2.0 → implicant-0.6.0}/src/implicant/manifest.py +31 -2
  19. implicant-0.6.0/src/implicant/models.py +99 -0
  20. implicant-0.6.0/src/implicant/samples/bankfraud.json +17 -0
  21. implicant-0.6.0/src/implicant/samples/cancer.json +32 -0
  22. implicant-0.6.0/src/implicant/samples/credit.json +46 -0
  23. implicant-0.6.0/src/implicant/samples/diabetes.json +11 -0
  24. {implicant-0.2.0 → implicant-0.6.0}/src/implicant/transport/base.py +0 -4
  25. {implicant-0.2.0 → implicant-0.6.0}/src/implicant/transport/http.py +40 -24
  26. {implicant-0.2.0 → implicant-0.6.0}/src/implicant/transport/mock.py +5 -49
  27. implicant-0.2.0/PKG-INFO +0 -85
  28. implicant-0.2.0/README.md +0 -63
  29. implicant-0.2.0/docs/INSTALL_SMOKE_TEST.md +0 -47
  30. implicant-0.2.0/docs/architecture.md +0 -247
  31. implicant-0.2.0/docs/binarization-spec-format.md +0 -230
  32. implicant-0.2.0/pyproject.toml +0 -79
  33. implicant-0.2.0/src/implicant/__main__.py +0 -5
  34. implicant-0.2.0/src/implicant/app/__init__.py +0 -8
  35. implicant-0.2.0/src/implicant/app/__main__.py +0 -61
  36. implicant-0.2.0/src/implicant/app/catalog.py +0 -32
  37. implicant-0.2.0/src/implicant/app/credentials.py +0 -40
  38. implicant-0.2.0/src/implicant/app/main_window.py +0 -258
  39. implicant-0.2.0/src/implicant/app/messages.py +0 -145
  40. implicant-0.2.0/src/implicant/app/preflight.py +0 -73
  41. implicant-0.2.0/src/implicant/app/wizard.py +0 -71
  42. implicant-0.2.0/src/implicant/app/worker.py +0 -72
  43. implicant-0.2.0/src/implicant/binarize.py +0 -169
  44. implicant-0.2.0/src/implicant/cli/main.py +0 -133
  45. implicant-0.2.0/src/implicant/keys.py +0 -172
  46. implicant-0.2.0/tests/_helut_caps.py +0 -16
  47. implicant-0.2.0/tests/fixtures/binarization/cancer_golden.json +0 -2230
  48. implicant-0.2.0/tests/fixtures/binarization/cancer_spec.json +0 -517
  49. implicant-0.2.0/tests/fixtures/binarization/diabetes_golden.json +0 -624
  50. implicant-0.2.0/tests/fixtures/binarization/diabetes_spec.json +0 -143
  51. implicant-0.2.0/tests/test_app_catalog.py +0 -21
  52. implicant-0.2.0/tests/test_app_credentials.py +0 -52
  53. implicant-0.2.0/tests/test_app_main_window.py +0 -115
  54. implicant-0.2.0/tests/test_app_messages.py +0 -85
  55. implicant-0.2.0/tests/test_app_preflight.py +0 -36
  56. implicant-0.2.0/tests/test_app_startup.py +0 -32
  57. implicant-0.2.0/tests/test_app_wizard.py +0 -32
  58. implicant-0.2.0/tests/test_app_worker.py +0 -60
  59. implicant-0.2.0/tests/test_binarize.py +0 -79
  60. implicant-0.2.0/tests/test_binarize_parity.py +0 -33
  61. implicant-0.2.0/tests/test_client_flow.py +0 -78
  62. implicant-0.2.0/tests/test_client_staged.py +0 -105
  63. implicant-0.2.0/tests/test_e2e_xbits.py +0 -117
  64. implicant-0.2.0/tests/test_envelope.py +0 -37
  65. implicant-0.2.0/tests/test_http_transport.py +0 -165
  66. implicant-0.2.0/tests/test_keys.py +0 -52
  67. implicant-0.2.0/tests/test_manifest.py +0 -75
  68. implicant-0.2.0/tests/test_postprocess.py +0 -44
  69. implicant-0.2.0/tests/test_seeded_key_size.py +0 -63
  70. {implicant-0.2.0 → implicant-0.6.0}/LICENSE +0 -0
  71. {implicant-0.2.0 → implicant-0.6.0}/src/implicant/cli/__init__.py +0 -0
  72. {implicant-0.2.0 → implicant-0.6.0}/src/implicant/envelope.py +0 -0
  73. {implicant-0.2.0 → implicant-0.6.0}/src/implicant/postprocess.py +0 -0
  74. {implicant-0.2.0 → implicant-0.6.0}/src/implicant/transport/__init__.py +0 -0
  75. {implicant-0.2.0 → implicant-0.6.0}/src/implicant/transport/errors.py +0 -0
@@ -0,0 +1,87 @@
1
+ name: publish to PyPI
2
+
3
+ # Mirrors implicant-fhe's publish workflow. Publishing is deliberately a
4
+ # separate, manual act — this never fires on a tag push. A PyPI upload is
5
+ # irreversible (the version number is burned even if the files are deleted),
6
+ # so building/tagging (`release.yml`) and publishing are kept apart.
7
+ #
8
+ # The gate is the dispatch itself rather than a protected environment: this org
9
+ # is on GitHub Free, where required-reviewers protection is a paid feature, and
10
+ # an unprotected `environment:` block would look like a gate while enforcing
11
+ # nothing.
12
+ on:
13
+ workflow_dispatch:
14
+ inputs:
15
+ tag:
16
+ description: "Release tag whose artifacts to publish (e.g. v0.5.0)"
17
+ required: true
18
+ type: string
19
+
20
+ permissions:
21
+ contents: read
22
+
23
+ jobs:
24
+ publish:
25
+ name: publish ${{ inputs.tag }}
26
+ runs-on: ubuntu-latest
27
+ permissions:
28
+ contents: read # read the release assets
29
+ id-token: write # OIDC token for PyPI Trusted Publishing
30
+ steps:
31
+ - uses: actions/checkout@v4
32
+ - uses: actions/setup-python@v5
33
+ with:
34
+ python-version: "3.12"
35
+
36
+ - name: Download artifacts from the release
37
+ env:
38
+ GH_TOKEN: ${{ github.token }}
39
+ run: |
40
+ set -euo pipefail
41
+ mkdir -p dist
42
+ version="${{ inputs.tag }}"
43
+ version="${version#v}"
44
+ # Pinned to the exact tag version: a release still carrying assets
45
+ # from an earlier build cannot be published by accident.
46
+ gh release download "${{ inputs.tag }}" \
47
+ --repo "${{ github.repository }}" \
48
+ --pattern "implicant-${version}*" \
49
+ --dir dist
50
+ ls -l dist/
51
+
52
+ - name: Refuse to publish artifacts that disagree with the tag
53
+ run: |
54
+ set -euo pipefail
55
+ version="${{ inputs.tag }}"
56
+ version="${version#v}"
57
+
58
+ count="$(find dist -type f \( -name '*.whl' -o -name '*.tar.gz' \) | wc -l)"
59
+ if [ "$count" -eq 0 ]; then
60
+ echo "::error::no artifacts attached to ${{ inputs.tag }}"
61
+ exit 1
62
+ fi
63
+
64
+ mismatched="$(find dist -type f ! -name "implicant-${version}-*.whl" \
65
+ ! -name "implicant-${version}.tar.gz")"
66
+ if [ -n "$mismatched" ]; then
67
+ echo "::error::artifacts do not match tag ${{ inputs.tag }}:"
68
+ echo "$mismatched"
69
+ exit 1
70
+ fi
71
+ echo "${count} artifact(s) staged for ${{ inputs.tag }}"
72
+
73
+ # The SDK is the client. It must never carry the evaluation engine's
74
+ # worker-only surface, nor a runtime requirement or index setting that
75
+ # would let a client resolve the private worker build. Same check the
76
+ # release build and the test suite run — repeated here because this is
77
+ # the last point before the upload becomes irreversible.
78
+ - name: Refuse to publish anything carrying the server surface
79
+ run: python scripts/check_no_server_surface.py dist
80
+
81
+ # No `password:` input — authentication is PyPI Trusted Publishing over
82
+ # OIDC, so there is no long-lived token stored in this repo. Configure the
83
+ # publisher once at
84
+ # https://pypi.org/manage/project/implicant/settings/publishing/
85
+ # with workflow name `publish-pypi.yml` and no environment.
86
+ - name: Publish to PyPI
87
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,66 @@
1
+ name: build release artifacts
2
+
3
+ # Mirrors implicant-fhe's `wheels.yml`: a tag push builds the distribution and
4
+ # cuts a GitHub release with the artifacts attached. It never publishes — that
5
+ # is a separate, manually dispatched act (`publish-pypi.yml`), because a PyPI
6
+ # upload is irreversible: the version number is burned even if the files are
7
+ # later deleted.
8
+ #
9
+ # This package is pure Python (hatchling), so there is no build matrix: one
10
+ # `py3-none-any` wheel plus an sdist, built once.
11
+ on:
12
+ push:
13
+ tags:
14
+ - "v*"
15
+ workflow_dispatch:
16
+
17
+ permissions:
18
+ contents: read
19
+
20
+ jobs:
21
+ build:
22
+ name: build wheel + sdist
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ - uses: actions/setup-python@v5
27
+ with:
28
+ python-version: "3.12"
29
+
30
+ - name: Build
31
+ run: |
32
+ python -m pip install --upgrade build
33
+ python -m build
34
+
35
+ # The SDK must never ship the evaluation engine's server surface. This is
36
+ # already asserted against the built wheel by
37
+ # `tests/test_no_server_surface.py`; repeat the check here so a release
38
+ # cannot be cut from a tree whose tests were skipped.
39
+ - name: Refuse to release anything carrying the server surface
40
+ run: python scripts/check_no_server_surface.py dist
41
+
42
+ - name: Check metadata
43
+ run: |
44
+ python -m pip install --upgrade twine
45
+ twine check dist/*
46
+
47
+ - uses: actions/upload-artifact@v4
48
+ with:
49
+ name: dist
50
+ path: dist/*
51
+
52
+ release:
53
+ name: attach to release
54
+ needs: [build]
55
+ runs-on: ubuntu-latest
56
+ if: startsWith(github.ref, 'refs/tags/v')
57
+ permissions:
58
+ contents: write
59
+ steps:
60
+ - uses: actions/download-artifact@v4
61
+ with:
62
+ path: dist
63
+ merge-multiple: true
64
+ - uses: softprops/action-gh-release@v2
65
+ with:
66
+ files: dist/*
@@ -45,3 +45,13 @@ CLAUDE.local.md
45
45
 
46
46
  # Staged implicant-fhe wheels (downloaded via `gh release download`, not committed)
47
47
  wheels/
48
+
49
+ # Briefcase / build logs
50
+ logs/
51
+
52
+ # Demo handoff bundle (large public-key blobs sent to platform; not committed).
53
+ # The .zip needs its own rule: the directory pattern above does not match it, so
54
+ # it stayed untracked-but-committable (460 MB) and hatchling swept it into the
55
+ # sdist (489 MB, past PyPI's 100 MB limit) until it was excluded explicitly.
56
+ /demo_handoff/
57
+ /demo_handoff.zip
@@ -0,0 +1,229 @@
1
+ Metadata-Version: 2.4
2
+ Name: implicant
3
+ Version: 0.6.0
4
+ Summary: Python client SDK for the Implicant FHE inference platform
5
+ Author: Implicant
6
+ License: Apache-2.0
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: httpx>=0.27
10
+ Requires-Dist: implicant-fhe<0.6,>=0.5
11
+ Requires-Dist: numpy>=1.26
12
+ Requires-Dist: pydantic>=2.6
13
+ Requires-Dist: rich>=13.8
14
+ Requires-Dist: typer>=0.12
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest-cov>=5; extra == 'dev'
17
+ Requires-Dist: pytest>=8; extra == 'dev'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # Implicant SDK
21
+
22
+ Python **client** for the Implicant FHE inference platform. Encrypts one row
23
+ locally under BGV, sends the ciphertext to the platform for homomorphic
24
+ evaluation, and decrypts the result. The server never sees plaintext; the
25
+ secret key never leaves the process.
26
+
27
+ The classifier head runs **server-side under FHE** (Variant B): the platform
28
+ returns one ciphertext with the class scores packed contiguously, which the
29
+ client decrypts (unsigned) and decodes with `decode_class_scores` before
30
+ threshold/argmax. `W` / `b` never reach the client.
31
+
32
+ Pure Python — all crypto comes from [`implicant-fhe`](https://github.com/implicant/implicant-fhe)
33
+ (`helut.client`). Contracts: [`docs/architecture.md`](docs/architecture.md)
34
+ and `platform/docs/FHE_INTERFACE_SPEC.md`.
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ pip install implicant
40
+ ```
41
+
42
+ Pulls `implicant-fhe` (the `helut` crypto wheel) from PyPI automatically. Wheels
43
+ ship for **CPython 3.10–3.14** on **macOS-arm64** and **manylinux x86_64** only.
44
+ On any other target — Windows, Linux aarch64, macOS x86_64 — there is no wheel
45
+ and no source fallback, so pip stops with `No matching distribution found for
46
+ implicant-fhe`.
47
+
48
+ ### Developers
49
+
50
+ ```bash
51
+ python3.13 -m venv .venv313
52
+ .venv313/bin/pip install -e ".[dev]"
53
+ .venv313/bin/pytest tests/ -v # 100 tests, ~3.5 min (real BGV keygen)
54
+ ```
55
+
56
+ To pin an unreleased `implicant-fhe` build, stage its wheel in `./wheels/` and
57
+ add `--find-links wheels/`; the published versions resolve from PyPI without it.
58
+
59
+ ## Running an inference (CLI)
60
+
61
+ The `implicant` CLI is the client-side workflow: configure once, then run
62
+ encrypted predictions. Everything stays local except the ciphertext and the
63
+ public keys — the secret key never leaves your machine.
64
+
65
+ ### 1. Point the client at the platform
66
+
67
+ ```bash
68
+ implicant init --api-url https://api.implicant.example
69
+ ```
70
+
71
+ Writes `~/.implicant/config.toml`. Run `implicant status` to confirm the
72
+ configured `api_url` and list any locally cached keys.
73
+
74
+ ### 2. Set your API token
75
+
76
+ The bearer token is read from the environment, never stored on disk:
77
+
78
+ ```bash
79
+ export IMPLICANT_API_KEY="imp_..."
80
+ ```
81
+
82
+ ### 3. Discover the available models
83
+
84
+ The SDK ships a hand-maintained registry of the models servable on the
85
+ platform (there is no server-side listing endpoint yet):
86
+
87
+ ```bash
88
+ $ implicant models list
89
+ cancer Predicts whether a breast tumor is malignant or benign
90
+ diabetes Predicts whether a patient has diabetes
91
+ ```
92
+
93
+ The listed slug is the `--model` value for `predict`.
94
+
95
+ ### 4. Prepare the input row
96
+
97
+ One row of raw feature values, as a JSON object (or a single-row CSV). The keys
98
+ are the feature names the model expects. Each known model ships a bundled
99
+ example row — the fastest way to get a correct starting point:
100
+
101
+ ```bash
102
+ implicant models sample diabetes --out row.json # or no --out to print to stdout
103
+ ```
104
+
105
+ ```json
106
+ {
107
+ "preg": 6,
108
+ "plas": 148,
109
+ "pres": 72,
110
+ "skin": 35,
111
+ "insu": 0,
112
+ "mass": 33.6,
113
+ "pedi": 0.627,
114
+ "age": 50
115
+ }
116
+ ```
117
+
118
+ Edit the values by hand to test different inputs, then feed the file to
119
+ `predict`. (`--out` refuses to overwrite an existing file unless you pass
120
+ `--force`.)
121
+
122
+ ### 5. Predict
123
+
124
+ ```bash
125
+ implicant predict --model diabetes --input row.json
126
+ ```
127
+
128
+ Or skip the file entirely and run on the bundled example row:
129
+
130
+ ```bash
131
+ implicant predict --model diabetes --sample
132
+ ```
133
+
134
+ This fetches the model manifest, generates (or reloads) your keys, uploads the
135
+ **public** keys on first use, encrypts the row locally, sends the ciphertext,
136
+ then decrypts and decodes the returned class scores. Progress streams to
137
+ stderr (with a spinner on the current step in a terminal) and the decision
138
+ lands on stdout:
139
+
140
+ ```
141
+ ✓ Fetching model configuration
142
+ ✓ Preparing encryption keys
143
+ ✓ Encrypting input data
144
+ ✓ Evaluating on the server (encrypted)
145
+ ✓ Decrypting server response
146
+ Final decision: tested_negative
147
+ ```
148
+
149
+ Add `--json` for the machine-readable result instead of the decision line
150
+ (progress stays on stderr, so piping stdout to `jq` is safe):
151
+
152
+ ```json
153
+ {
154
+ "key_id": "bgv-n32768-L4-a1b2c3d4e5f6",
155
+ "label_index": 0,
156
+ "label": "tested_negative",
157
+ "scores": [-6684]
158
+ }
159
+ ```
160
+
161
+ Add `--no-persist-key` to use an ephemeral secret key for a single run (nothing
162
+ written to disk; keys are regenerated and re-uploaded next time).
163
+
164
+ ### Managing keys
165
+
166
+ The first prediction for a `key_id` persists your secret key to
167
+ `~/.implicant/keys/<key_id>/secret_key.bin` (mode `0600`) so later runs skip
168
+ keygen. Inspect and clean up the local store:
169
+
170
+ ```bash
171
+ implicant keys list # list key_ids; flags which have a persisted SK
172
+ implicant keys rm <key_id> # delete one stored key (secret + public)
173
+ implicant keys purge --yes # delete all stored keys
174
+ ```
175
+
176
+ ## Use (Python)
177
+
178
+ The same flow is available programmatically:
179
+
180
+ ```python
181
+ from implicant import ImplicantClient, list_known_models, sample_row
182
+ from implicant.transport import HttpxTransport
183
+
184
+ for m in list_known_models(): # discover: (slug, description) pairs
185
+ print(m.slug, "—", m.name)
186
+
187
+ row = sample_row("diabetes") # fresh mutable copy of the bundled example
188
+ row["age"] = 61 # hand-tweak values for testing
189
+
190
+ client = ImplicantClient(
191
+ HttpxTransport(base_url="https://api.implicant.example", api_key="imp_..."),
192
+ key_cache_dir="~/.implicant/keys",
193
+ )
194
+ result = client.predict("diabetes", row, class_names=("negative", "positive"))
195
+ print(result.prediction.label)
196
+ ```
197
+
198
+ ## Demo mode
199
+
200
+ Demo mode skips FHE key generation by loading a pre-generated keypair shipped
201
+ inside the package, and skips the public-key upload (the platform already holds
202
+ the matching public bundle). It exists so a prediction can run immediately in a
203
+ live demo without the ~250 s keygen delay.
204
+
205
+ Activate with an environment variable (applies to the whole session):
206
+
207
+ ```bash
208
+ export IMPLICANT_DEMO=1
209
+ implicant predict -m cancer -i row.json
210
+ ```
211
+
212
+ Or per-command — combined with `--sample`, this is a zero-setup end-to-end run:
213
+
214
+ ```bash
215
+ implicant predict -m cancer --sample --demo
216
+ ```
217
+
218
+ Only the provisioned demo models (`cancer`, `diabetes`) have bundled keys; other
219
+ model IDs raise an error in demo mode.
220
+
221
+ > **Security warning — demo mode is not private.** The demo secret key is
222
+ > shipped inside the package and is therefore public: anyone with the wheel can
223
+ > decrypt anything encrypted in demo mode. **Never use demo mode for real or
224
+ > sensitive data.** Normal mode (the default) generates a secret key that never
225
+ > leaves your machine.
226
+
227
+ ## License
228
+
229
+ Apache-2.0.
@@ -0,0 +1,210 @@
1
+ # Implicant SDK
2
+
3
+ Python **client** for the Implicant FHE inference platform. Encrypts one row
4
+ locally under BGV, sends the ciphertext to the platform for homomorphic
5
+ evaluation, and decrypts the result. The server never sees plaintext; the
6
+ secret key never leaves the process.
7
+
8
+ The classifier head runs **server-side under FHE** (Variant B): the platform
9
+ returns one ciphertext with the class scores packed contiguously, which the
10
+ client decrypts (unsigned) and decodes with `decode_class_scores` before
11
+ threshold/argmax. `W` / `b` never reach the client.
12
+
13
+ Pure Python — all crypto comes from [`implicant-fhe`](https://github.com/implicant/implicant-fhe)
14
+ (`helut.client`). Contracts: [`docs/architecture.md`](docs/architecture.md)
15
+ and `platform/docs/FHE_INTERFACE_SPEC.md`.
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ pip install implicant
21
+ ```
22
+
23
+ Pulls `implicant-fhe` (the `helut` crypto wheel) from PyPI automatically. Wheels
24
+ ship for **CPython 3.10–3.14** on **macOS-arm64** and **manylinux x86_64** only.
25
+ On any other target — Windows, Linux aarch64, macOS x86_64 — there is no wheel
26
+ and no source fallback, so pip stops with `No matching distribution found for
27
+ implicant-fhe`.
28
+
29
+ ### Developers
30
+
31
+ ```bash
32
+ python3.13 -m venv .venv313
33
+ .venv313/bin/pip install -e ".[dev]"
34
+ .venv313/bin/pytest tests/ -v # 100 tests, ~3.5 min (real BGV keygen)
35
+ ```
36
+
37
+ To pin an unreleased `implicant-fhe` build, stage its wheel in `./wheels/` and
38
+ add `--find-links wheels/`; the published versions resolve from PyPI without it.
39
+
40
+ ## Running an inference (CLI)
41
+
42
+ The `implicant` CLI is the client-side workflow: configure once, then run
43
+ encrypted predictions. Everything stays local except the ciphertext and the
44
+ public keys — the secret key never leaves your machine.
45
+
46
+ ### 1. Point the client at the platform
47
+
48
+ ```bash
49
+ implicant init --api-url https://api.implicant.example
50
+ ```
51
+
52
+ Writes `~/.implicant/config.toml`. Run `implicant status` to confirm the
53
+ configured `api_url` and list any locally cached keys.
54
+
55
+ ### 2. Set your API token
56
+
57
+ The bearer token is read from the environment, never stored on disk:
58
+
59
+ ```bash
60
+ export IMPLICANT_API_KEY="imp_..."
61
+ ```
62
+
63
+ ### 3. Discover the available models
64
+
65
+ The SDK ships a hand-maintained registry of the models servable on the
66
+ platform (there is no server-side listing endpoint yet):
67
+
68
+ ```bash
69
+ $ implicant models list
70
+ cancer Predicts whether a breast tumor is malignant or benign
71
+ diabetes Predicts whether a patient has diabetes
72
+ ```
73
+
74
+ The listed slug is the `--model` value for `predict`.
75
+
76
+ ### 4. Prepare the input row
77
+
78
+ One row of raw feature values, as a JSON object (or a single-row CSV). The keys
79
+ are the feature names the model expects. Each known model ships a bundled
80
+ example row — the fastest way to get a correct starting point:
81
+
82
+ ```bash
83
+ implicant models sample diabetes --out row.json # or no --out to print to stdout
84
+ ```
85
+
86
+ ```json
87
+ {
88
+ "preg": 6,
89
+ "plas": 148,
90
+ "pres": 72,
91
+ "skin": 35,
92
+ "insu": 0,
93
+ "mass": 33.6,
94
+ "pedi": 0.627,
95
+ "age": 50
96
+ }
97
+ ```
98
+
99
+ Edit the values by hand to test different inputs, then feed the file to
100
+ `predict`. (`--out` refuses to overwrite an existing file unless you pass
101
+ `--force`.)
102
+
103
+ ### 5. Predict
104
+
105
+ ```bash
106
+ implicant predict --model diabetes --input row.json
107
+ ```
108
+
109
+ Or skip the file entirely and run on the bundled example row:
110
+
111
+ ```bash
112
+ implicant predict --model diabetes --sample
113
+ ```
114
+
115
+ This fetches the model manifest, generates (or reloads) your keys, uploads the
116
+ **public** keys on first use, encrypts the row locally, sends the ciphertext,
117
+ then decrypts and decodes the returned class scores. Progress streams to
118
+ stderr (with a spinner on the current step in a terminal) and the decision
119
+ lands on stdout:
120
+
121
+ ```
122
+ ✓ Fetching model configuration
123
+ ✓ Preparing encryption keys
124
+ ✓ Encrypting input data
125
+ ✓ Evaluating on the server (encrypted)
126
+ ✓ Decrypting server response
127
+ Final decision: tested_negative
128
+ ```
129
+
130
+ Add `--json` for the machine-readable result instead of the decision line
131
+ (progress stays on stderr, so piping stdout to `jq` is safe):
132
+
133
+ ```json
134
+ {
135
+ "key_id": "bgv-n32768-L4-a1b2c3d4e5f6",
136
+ "label_index": 0,
137
+ "label": "tested_negative",
138
+ "scores": [-6684]
139
+ }
140
+ ```
141
+
142
+ Add `--no-persist-key` to use an ephemeral secret key for a single run (nothing
143
+ written to disk; keys are regenerated and re-uploaded next time).
144
+
145
+ ### Managing keys
146
+
147
+ The first prediction for a `key_id` persists your secret key to
148
+ `~/.implicant/keys/<key_id>/secret_key.bin` (mode `0600`) so later runs skip
149
+ keygen. Inspect and clean up the local store:
150
+
151
+ ```bash
152
+ implicant keys list # list key_ids; flags which have a persisted SK
153
+ implicant keys rm <key_id> # delete one stored key (secret + public)
154
+ implicant keys purge --yes # delete all stored keys
155
+ ```
156
+
157
+ ## Use (Python)
158
+
159
+ The same flow is available programmatically:
160
+
161
+ ```python
162
+ from implicant import ImplicantClient, list_known_models, sample_row
163
+ from implicant.transport import HttpxTransport
164
+
165
+ for m in list_known_models(): # discover: (slug, description) pairs
166
+ print(m.slug, "—", m.name)
167
+
168
+ row = sample_row("diabetes") # fresh mutable copy of the bundled example
169
+ row["age"] = 61 # hand-tweak values for testing
170
+
171
+ client = ImplicantClient(
172
+ HttpxTransport(base_url="https://api.implicant.example", api_key="imp_..."),
173
+ key_cache_dir="~/.implicant/keys",
174
+ )
175
+ result = client.predict("diabetes", row, class_names=("negative", "positive"))
176
+ print(result.prediction.label)
177
+ ```
178
+
179
+ ## Demo mode
180
+
181
+ Demo mode skips FHE key generation by loading a pre-generated keypair shipped
182
+ inside the package, and skips the public-key upload (the platform already holds
183
+ the matching public bundle). It exists so a prediction can run immediately in a
184
+ live demo without the ~250 s keygen delay.
185
+
186
+ Activate with an environment variable (applies to the whole session):
187
+
188
+ ```bash
189
+ export IMPLICANT_DEMO=1
190
+ implicant predict -m cancer -i row.json
191
+ ```
192
+
193
+ Or per-command — combined with `--sample`, this is a zero-setup end-to-end run:
194
+
195
+ ```bash
196
+ implicant predict -m cancer --sample --demo
197
+ ```
198
+
199
+ Only the provisioned demo models (`cancer`, `diabetes`) have bundled keys; other
200
+ model IDs raise an error in demo mode.
201
+
202
+ > **Security warning — demo mode is not private.** The demo secret key is
203
+ > shipped inside the package and is therefore public: anyone with the wheel can
204
+ > decrypt anything encrypted in demo mode. **Never use demo mode for real or
205
+ > sensitive data.** Normal mode (the default) generates a secret key that never
206
+ > leaves your machine.
207
+
208
+ ## License
209
+
210
+ Apache-2.0.
@@ -0,0 +1,79 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "implicant"
7
+ version = "0.6.0"
8
+ description = "Python client SDK for the Implicant FHE inference platform"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ authors = [{ name = "Implicant" }]
12
+ license = { text = "Apache-2.0" }
13
+ license-files = ["LICENSE"]
14
+ dependencies = [
15
+ # FHE primitives (keygen, encrypt, decrypt, Params, key_id) live here.
16
+ # The SDK is a pure-Python client over `helut.client`. Floor is 0.5.0:
17
+ # that release moves the kernel to SEAL 4.4.3, which rotates every
18
+ # canonical key_id (seal_version "4.3" -> "4.4" in the params hash), so
19
+ # the bundled demo keys only exist under 0.5-era key_ids. The 0.4-era
20
+ # client/server wheel split still holds: both distributions satisfy this
21
+ # specifier, and only the public client build is reachable from PyPI.
22
+ "implicant-fhe>=0.5,<0.6",
23
+ "httpx>=0.27",
24
+ "pydantic>=2.6",
25
+ "typer>=0.12",
26
+ # Progress rendering in the CLI (typer already requires it; declared
27
+ # because we import it directly).
28
+ "rich>=13.8",
29
+ "numpy>=1.26",
30
+ ]
31
+
32
+ [project.optional-dependencies]
33
+ # The worker (server-capable) build of implicant-fhe — carrying the
34
+ # `+server` local version once implicant-fhe's client/server wheel split
35
+ # lands — belongs ONLY here, never in [project.dependencies]. The SDK's own
36
+ # test suite legitimately drives the real engine (tests/_server_harness.py)
37
+ # on a dev machine; a client installing `implicant` from PyPI must never be
38
+ # able to resolve the worker build even by accident. Enforced against the
39
+ # BUILT wheel by tests/test_no_server_surface.py. See
40
+ # implicant-fhe/docs/superpowers/plans/2026-07-31-client-server-wheel-split.md
41
+ # Task W5. The runtime floor is 0.5.0 (see [project.dependencies]); both
42
+ # distributions satisfy it, and PyPI structurally refuses the worker build's
43
+ # local version, so a client cannot resolve it.
44
+ dev = [
45
+ "pytest>=8",
46
+ "pytest-cov>=5",
47
+ ]
48
+
49
+ [project.scripts]
50
+ implicant = "implicant.cli.main:app"
51
+
52
+ [tool.hatch.build.targets.wheel]
53
+ packages = ["src/implicant"]
54
+ # Demo keypairs are large .bin blobs (typically VCS-ignored); force-include them
55
+ # so demo mode can load them at runtime. Public material only + a PUBLIC demo SK.
56
+ artifacts = ["src/implicant/demo_keys/**/*.bin"]
57
+
58
+ [tool.hatch.build.targets.sdist]
59
+ # Keep internal dev context, local tooling, and the non-functional demo stub
60
+ # out of the published source dist. `demo_handoff.zip` is a 482 MB bundle of
61
+ # public key blobs staged for the platform; the sibling `demo_handoff/` dir is
62
+ # VCS-ignored but the zip is not, so without this it lands in the sdist and
63
+ # pushes it to ~489 MB — far past PyPI's 100 MB per-file limit.
64
+ # `tests/` and `scripts/` are excluded for a SECURITY reason, not tidiness:
65
+ # `tests/_server_harness.py` and the w6_* probes wire the engine's worker-only
66
+ # facade into a working local round trip. That recipe was
67
+ # deliberately removed from the shipped package (audit finding B1, wheel-split
68
+ # task W5); shipping it in the sdist would hand it straight back. `docs/` goes
69
+ # too — it references the private repo and internal design notes.
70
+ # `scripts/check_no_server_surface.py` enforces this on every release.
71
+ exclude = [
72
+ "CLAUDE.md",
73
+ ".claude",
74
+ "docs",
75
+ "tests",
76
+ "scripts",
77
+ "examples",
78
+ "demo_handoff.zip",
79
+ ]