mooring 0.3.2__tar.gz → 0.3.4__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.
- {mooring-0.3.2 → mooring-0.3.4}/PKG-INFO +3 -1
- {mooring-0.3.2 → mooring-0.3.4}/docs/admins/ai-privacy.md +151 -9
- {mooring-0.3.2 → mooring-0.3.4}/docs/admins/configuration.md +26 -0
- {mooring-0.3.2 → mooring-0.3.4}/docs/users/ai-copilot.md +11 -0
- {mooring-0.3.2 → mooring-0.3.4}/docs/users/cli.md +20 -0
- {mooring-0.3.2 → mooring-0.3.4}/pyproject.toml +8 -1
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/__init__.py +1 -1
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/ai/base.py +6 -0
- mooring-0.3.4/src/mooring/ai/chat.py +391 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/ai/context.py +42 -16
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/ai/copilot.py +12 -0
- mooring-0.3.4/src/mooring/ai/ner.py +324 -0
- mooring-0.3.4/src/mooring/ai/pii.py +316 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/ai/session.py +34 -1
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/ai/tools.py +15 -2
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/cli.py +273 -1
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/config.py +46 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/config_default.toml +39 -2
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/config_store.py +70 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/hub/server.py +130 -22
- mooring-0.3.4/src/mooring/hub/static/chat.html +50 -0
- mooring-0.3.4/src/mooring/hub/static/chat.js +995 -0
- mooring-0.3.4/src/mooring/hub/static/chat_core.js +177 -0
- mooring-0.3.4/src/mooring/hub/static/style.css +427 -0
- mooring-0.3.4/tests/js/chat_core.test.js +140 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_ai_tools.py +22 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_chat_session.py +52 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_config_store.py +70 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_hub.py +47 -0
- mooring-0.3.4/tests/test_ner.py +132 -0
- mooring-0.3.4/tests/test_pii.py +543 -0
- {mooring-0.3.2 → mooring-0.3.4}/uv.lock +813 -2
- mooring-0.3.2/src/mooring/ai/chat.py +0 -153
- mooring-0.3.2/src/mooring/hub/static/chat.html +0 -51
- mooring-0.3.2/src/mooring/hub/static/chat.js +0 -414
- mooring-0.3.2/src/mooring/hub/static/style.css +0 -272
- {mooring-0.3.2 → mooring-0.3.4}/.github/workflows/docs.yml +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/.github/workflows/release.yml +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/.gitignore +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/README.md +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/docs/admins/build-and-distribute.md +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/docs/admins/github-setup.md +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/docs/admins/index.md +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/docs/assets/images/anchor-mark.svg +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/docs/assets/images/favicon.svg +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/docs/assets/javascripts/landing.js +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/docs/assets/stylesheets/landing.css +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/docs/assets/stylesheets/oah-theme.css +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/docs/developers/contributing.md +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/docs/developers/index.md +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/docs/index.md +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/docs/users/conflicts.md +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/docs/users/daily-workflow.md +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/docs/users/index.md +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/docs/users/power-bi.md +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/overrides/home.html +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/scripts/release.ps1 +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/scripts/spike_copilot_chat.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/scripts/spike_live_introspect.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/scripts/spike_marimo_filewatch.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/scripts/spike_marimo_http_control.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/ai/__init__.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/ai/cellwrite.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/ai/codeblocks.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/ai/datadictionary.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/ai/introspect.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/ai/locality.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/ai/prompt.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/ai/secrets.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/auth.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/deletion.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/editor.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/githost.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/github.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/gitsha.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/hub/__init__.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/hub/static/app.js +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/hub/static/index.html +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/manifest.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/notebook_template.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/paths.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/pbip.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/pyproject_env.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/schema.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/sync.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/src/mooring/telemetry.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/conftest.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/manual_editor_check.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_ai_dict_tools.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_auth.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_cellwrite.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_chat_context.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_cli_delete.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_cli_deps.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_cli_repo.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_codeblocks.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_config.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_context.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_datadictionary.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_deletion.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_editor.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_editor_marimo_ai.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_githost.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_github.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_gitsha.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_introspect.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_locality.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_manifest.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_paths.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_pbip.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_pyproject_env.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_schema.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_secrets.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_sync.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_telemetry.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/tests/test_truststore.py +0 -0
- {mooring-0.3.2 → mooring-0.3.4}/zensical.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mooring
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Git-free marimo notebook sharing via GitHub
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Requires-Dist: keyring
|
|
@@ -14,6 +14,8 @@ Requires-Dist: truststore
|
|
|
14
14
|
Requires-Dist: uvicorn
|
|
15
15
|
Provides-Extra: copilot
|
|
16
16
|
Requires-Dist: github-copilot-sdk>=1.0.1; extra == 'copilot'
|
|
17
|
+
Provides-Extra: pii
|
|
18
|
+
Requires-Dist: gliner>=0.2.13; extra == 'pii'
|
|
17
19
|
Description-Content-Type: text/markdown
|
|
18
20
|
|
|
19
21
|
# ⚓ mooring
|
|
@@ -39,11 +39,15 @@ sees whatever your team wrote into those files.
|
|
|
39
39
|
|
|
40
40
|
## The four structural guarantees
|
|
41
41
|
|
|
42
|
-
1. **Single choke point.** The context handed to the model
|
|
43
|
-
place (`ai/chat.py:build_system_context`) — from the schema
|
|
44
|
-
notebook source, plus (only when team context is enabled) the team
|
|
45
|
-
and the value-minimised data-dictionary slice.
|
|
46
|
-
|
|
42
|
+
1. **Single choke point for the system context.** The context handed to the model
|
|
43
|
+
is assembled in one place (`ai/chat.py:build_system_context`) — from the schema
|
|
44
|
+
text and the notebook source, plus (only when team context is enabled) the team
|
|
45
|
+
instructions and the value-minimised data-dictionary slice. Two further egresses
|
|
46
|
+
exist by design and are value-free by construction: your **chat turns**, and the
|
|
47
|
+
agent's **tool reads** (it can re-read the notebook source via
|
|
48
|
+
`mooring_read_notebook_source`, and fetch dataset schemas). The opt-in
|
|
49
|
+
[structured-PII scan](#structured-pii-pre-flight-scan-opt-in-best-effort) runs at
|
|
50
|
+
all of these, not only `build_system_context`.
|
|
47
51
|
2. **Value-free tools only.** The agent is given mooring's own tools (`ai/tools.py`):
|
|
48
52
|
list datasets, get a schema, read the notebook source, and *propose* a cell —
|
|
49
53
|
each value-free by construction. When a data dictionary is configured, three more
|
|
@@ -81,9 +85,11 @@ data often lives **outside** it — a network share, a warehouse export, a datab
|
|
|
81
85
|
connection, a path built at runtime — and the schema most useful for writing code
|
|
82
86
|
is frequently a *derived* frame (a join/filter result) that no file holds. To help
|
|
83
87
|
there, mooring can read the schema of the dataframes **already loaded in your
|
|
84
|
-
running kernel**. It is **on by default
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
running kernel**. It is **on by default**, refreshed on every chat turn (so a frame
|
|
89
|
+
you load after opening the chat is picked up without reopening), and value-free — but,
|
|
90
|
+
like team context, its safety comes from *how it is built*, not from physical
|
|
91
|
+
impossibility, so it is documented here in full. Turn it off with
|
|
92
|
+
`[ai] live_schema = false`.
|
|
87
93
|
|
|
88
94
|
How it stays value-blind (`ai/introspect.py`):
|
|
89
95
|
|
|
@@ -100,6 +106,11 @@ How it stays value-blind (`ai/introspect.py`):
|
|
|
100
106
|
- **Fail-closed on the way back.** The reader (`_parse_frames`) accepts only the
|
|
101
107
|
`{name, columns: [[str, str]], n_rows: int}` shape and drops everything else, so a
|
|
102
108
|
value can't ride back on a key mooring doesn't read.
|
|
109
|
+
- **The per-turn refresh adds no new value channel.** The schema is captured at
|
|
110
|
+
chat-open *and* re-probed on each turn through the **same** frozen probe and
|
|
111
|
+
fail-closed reader; a turn re-states the schema only when the kernel changed, and an
|
|
112
|
+
unchanged kernel is not re-sent. The refresh re-states already-value-free schema —
|
|
113
|
+
it opens no path a value could take that the open-time capture did not.
|
|
103
114
|
|
|
104
115
|
Honest caveat: unlike `schema.py` (which physically only ever reads a file header),
|
|
105
116
|
this probe runs in a namespace that *contains* values. Its value-blindness is the
|
|
@@ -145,13 +156,139 @@ Run `mooring ai dictionary check` to see exactly how your files parse — which
|
|
|
145
156
|
was detected, how many tables/columns were kept, which keys were dropped, and any
|
|
146
157
|
secret-scan findings — *before* enabling the feature or sharing the files.
|
|
147
158
|
|
|
159
|
+
## Structured-PII pre-flight scan (opt-in, best-effort)
|
|
160
|
+
|
|
161
|
+
The guarantees above stop the *data* from reaching the model. They cannot stop a
|
|
162
|
+
human from **typing a real value** into a cell or the chat —
|
|
163
|
+
`df.filter(pl.col("pan") == "4012 8888 8888 1881")`, or "why does account
|
|
164
|
+
4012888888881881 fail?". As a thin extra floor, mooring can scan text on its way
|
|
165
|
+
out for **well-formed structured identifiers** and warn before it leaves. It is
|
|
166
|
+
**off by default** (`[ai.pii] enabled = false`) and, like team context, its safety
|
|
167
|
+
is best-effort, not structural.
|
|
168
|
+
|
|
169
|
+
**What it catches** (precision over recall): checksum-validated **payment cards**
|
|
170
|
+
(Luhn), **IBANs** (mod-97), and **NHS numbers** (mod-11), plus shape-anchored
|
|
171
|
+
**emails** and **UK NINOs**. **What it does not catch, by design:** addresses,
|
|
172
|
+
account narratives, **UK sort codes**, **bank account numbers**, US SSNs, phone
|
|
173
|
+
numbers, dates of birth, IP addresses, or any value split across two messages.
|
|
174
|
+
Person **names** are out of reach of the structured scan too, but can be caught by
|
|
175
|
+
the optional local-NER pass below. **A clean scan is not a value-free guarantee** —
|
|
176
|
+
it is a safety net for the obvious, well-formed cases, and it complements (never
|
|
177
|
+
replaces) the structural value-blindness above.
|
|
178
|
+
|
|
179
|
+
It runs at every egress, and every finding is value-free — a line number and a
|
|
180
|
+
*kind* (`payment card`, `email address`, …), never the matched value:
|
|
181
|
+
|
|
182
|
+
- **Your chat prompt.** With `block_prompt = true` (the default once the feature is
|
|
183
|
+
on), a prompt that looks like it contains a card/IBAN/NHS/email/NINO is **held**;
|
|
184
|
+
you see which kinds tripped it and must click **"Send anyway"** — nothing reaches
|
|
185
|
+
the model until you confirm. (Set `block_prompt = false` for a warn-only advisory.)
|
|
186
|
+
- **The notebook source and its schema.** On opening the copilot you get a one-time,
|
|
187
|
+
value-free banner if the notebook or a dataset schema looks like it contains PII.
|
|
188
|
+
The source is never rewritten (that would break your code). But a schema **column
|
|
189
|
+
name** that is itself a value — the result of a pivot/transpose on a PII key, e.g.
|
|
190
|
+
`df.pivot(on="customer_pan")` — is **withheld** from the schema the model sees.
|
|
191
|
+
- **Team context.** An `instructions.md` carrying a checksum-validated card/IBAN/NHS
|
|
192
|
+
(or a secret) is withheld entirely; a stray email/NINO drops just that line; a
|
|
193
|
+
data-dictionary description that trips the scan is dropped.
|
|
194
|
+
|
|
195
|
+
Run **`mooring ai pii check`** to scan your `context/` files and notebook sources
|
|
196
|
+
**offline** (no Copilot, no network) before enabling the feature — it prints
|
|
197
|
+
`path:line kind` for each finding and never echoes a value. Put `# mooring: pii-ok`
|
|
198
|
+
on a line to retire a reviewed false positive.
|
|
199
|
+
|
|
200
|
+
Configure it under `[ai.pii]`: `enabled` (master switch), `block_prompt`
|
|
201
|
+
(hold-and-confirm vs. a warn-only advisory on the chat prompt), and
|
|
202
|
+
`scan_notebook_source` (the source/schema banner).
|
|
203
|
+
|
|
204
|
+
## Name detection (opt-in, local NER)
|
|
205
|
+
|
|
206
|
+
A person's name — `where name == "Jane Smith"` — has no checksum or fixed shape, so
|
|
207
|
+
the structured scan above cannot see it. The optional **name pass** (`ai/ner.py`)
|
|
208
|
+
closes that gap with a **local** zero-shot NER model ([GLiNER](https://github.com/urchade/GLiNER)),
|
|
209
|
+
shipped as the `mooring[pii]` extra so the lean install and the frozen `.pyz` stay
|
|
210
|
+
free of the heavy ML stack (torch + transformers). It is **off by default**, only
|
|
211
|
+
acts when `[ai.pii] enabled` is also true, and is **best-effort** (NER both misses
|
|
212
|
+
and false-positives — a clean scan is not proof of no names).
|
|
213
|
+
|
|
214
|
+
Its privacy properties match the structured scan:
|
|
215
|
+
|
|
216
|
+
- **Local only.** The model runs on the analyst's machine; the text is never sent
|
|
217
|
+
anywhere to be scanned. The single network touch is a **one-time model download**
|
|
218
|
+
from Hugging Face on first use — pre-fetch it on a managed/offline network with
|
|
219
|
+
**`mooring ai pii model`**.
|
|
220
|
+
- **Value-free findings.** GLiNER returns the matched name; mooring reads **only**
|
|
221
|
+
the label and character offset, maps it to a line number, and **drops the text**.
|
|
222
|
+
A finding is `(line, "person name")` — never the name — so it logs and streams
|
|
223
|
+
over SSE as safely as the structured kinds. Pinned by `tests/test_ner.py`.
|
|
224
|
+
- **No pickle, pinned.** The default model (`gliner-community/gliner_small-v2.5`) is
|
|
225
|
+
loaded as its **safetensors** `bf16` variant — `mooring ai pii model` fetches *only*
|
|
226
|
+
the safetensors file, never the repo's `pytorch_model.bin`, so nothing is unpickled.
|
|
227
|
+
It is **pinned to a specific commit** (`name_model_revision`) for reproducibility and
|
|
228
|
+
so a security review is against a fixed artifact.
|
|
229
|
+
- **Same egress + UI.** A flagged chat prompt is held with the same "Send anyway"
|
|
230
|
+
confirm; `mooring ai pii check` runs the name pass too (when the model is already
|
|
231
|
+
cached) for the offline lint. At the chat prompt, a configured-but-uninstalled extra
|
|
232
|
+
**fails loud** (a `scan_error` advisory) rather than silently doing nothing; while the
|
|
233
|
+
model is still downloading the name pass is skipped (the message is still structurally
|
|
234
|
+
scanned) and the chat shows a "preparing model" status.
|
|
235
|
+
|
|
236
|
+
Configure under `[ai.pii]`: `detect_names` (on/off), `name_model` / `name_model_revision`
|
|
237
|
+
/ `name_model_variant` (which model, pinned commit, and safetensors variant —
|
|
238
|
+
`name_model_variant = ""` loads a repo's default weights file for a model that has no
|
|
239
|
+
variant safetensors), `name_labels` (entity labels to flag), and `name_threshold`
|
|
240
|
+
(confidence cut-off; raise for fewer, safer hits). GLiNER is zero-shot, so `name_labels`
|
|
241
|
+
is not limited to people — add `"organization"` to also flag **business names** (surfaced
|
|
242
|
+
as an `organization` finding); other entity types (e.g. `"address"`) work the same way.
|
|
243
|
+
Capitalised non-person terms make organisation detection more false-positive-prone, so it
|
|
244
|
+
stays out of the default. Install and enable:
|
|
245
|
+
|
|
246
|
+
```toml
|
|
247
|
+
[ai.pii]
|
|
248
|
+
enabled = true
|
|
249
|
+
detect_names = true
|
|
250
|
+
```
|
|
251
|
+
```
|
|
252
|
+
pip install mooring[pii] # or: uvx mooring[pii] (combine extras: mooring[copilot,pii])
|
|
253
|
+
mooring ai pii model # pre-download the model (recommended)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Deploying name detection in an institutional / offline environment
|
|
257
|
+
|
|
258
|
+
The model download is the only part of mooring that reaches a non-GitHub host
|
|
259
|
+
(Hugging Face). In a locked-down environment, plan for:
|
|
260
|
+
|
|
261
|
+
- **Firewall allow-list.** Outbound HTTPS is needed to `huggingface.co` **and** the
|
|
262
|
+
file backends — the LFS CDN and the newer **Xet** hosts (`cas-bridge.xethub.hf.co`,
|
|
263
|
+
`*.xethub.hf.co`). Allow-listing only `huggingface.co` passes the metadata fetch and
|
|
264
|
+
then fails on the actual download.
|
|
265
|
+
- **TLS / SSL-intercepting proxy.** mooring enables the **OS trust store** globally
|
|
266
|
+
(`truststore`), so Hugging Face traffic honours your proxy's root CA automatically,
|
|
267
|
+
the same way GitHub traffic does — no separate CA bundle needed in the normal case.
|
|
268
|
+
`REQUESTS_CA_BUNDLE` / `SSL_CERT_FILE` still take precedence if you set them.
|
|
269
|
+
- **Proxy / rate limits.** `HTTPS_PROXY` / `NO_PROXY` are honoured; set an `HF_TOKEN`
|
|
270
|
+
to lift the anonymous-download rate limit (faster, fewer throttles).
|
|
271
|
+
- **Air-gapped (no egress).** Either point at an internal mirror with
|
|
272
|
+
`HF_ENDPOINT=https://<your-hf-proxy>` (e.g. Artifactory/Nexus), **or** provision the
|
|
273
|
+
cache out-of-band: run `mooring ai pii model` on a connected machine, copy
|
|
274
|
+
`~/.cache/huggingface` (or set a shared `HF_HOME`) to the target machines, and set
|
|
275
|
+
`HF_HUB_OFFLINE=1`. Relocate the cache with `HF_HOME` if the user profile is small
|
|
276
|
+
or roaming.
|
|
277
|
+
- **Model governance.** The weights are a third-party artifact. The pinned default is
|
|
278
|
+
safetensors (no code-execution-on-load risk that a pickle `pytorch_model.bin` carries),
|
|
279
|
+
loaded locally; review the pinned `name_model` + `name_model_revision` through your
|
|
280
|
+
model-risk process, and re-pin a new revision only after review.
|
|
281
|
+
|
|
148
282
|
## The one thing to watch
|
|
149
283
|
|
|
150
284
|
Anything **you type into a cell or the chat** is, by definition, visible to the
|
|
151
285
|
assistant. If you hard-code a real value into a cell —
|
|
152
286
|
`df.filter(pl.col("ssn") == "123-45-6789")` — that literal is part of the source
|
|
153
287
|
the assistant can read. The chat reminds you of this; **never paste real values**.
|
|
154
|
-
|
|
288
|
+
The opt-in [structured-PII scan](#structured-pii-pre-flight-scan-opt-in-best-effort)
|
|
289
|
+
above catches *well-formed* cards/IBANs/NHS numbers/emails/NINOs as a safety net,
|
|
290
|
+
but it cannot catch a name, a sort code, an account number, or a value typed into
|
|
291
|
+
prose — so the rule stands regardless.
|
|
155
292
|
|
|
156
293
|
## Verifying it yourself
|
|
157
294
|
|
|
@@ -177,3 +314,8 @@ The copilot needs the optional extra (`pip install mooring[copilot]` /
|
|
|
177
314
|
`uvx mooring[copilot]`), a GitHub Copilot licence (`mooring ai login`), and your
|
|
178
315
|
organisation's Copilot **CLI/agent policy** enabled. See
|
|
179
316
|
[Configuration](configuration.md) for the `[ai]` settings.
|
|
317
|
+
|
|
318
|
+
Optional **name detection** (the structured-PII guard's NER pass) needs the
|
|
319
|
+
separate `mooring[pii]` extra (`pip install mooring[pii]`); without it the guard
|
|
320
|
+
still does its stdlib structured-PII scan. See
|
|
321
|
+
[Name detection](#name-detection-opt-in-local-ner).
|
|
@@ -189,6 +189,32 @@ a fallback when no OS credential store is available; normally it's in the
|
|
|
189
189
|
credential store instead. Run `selftest` to print the exact paths on a given
|
|
190
190
|
machine.
|
|
191
191
|
|
|
192
|
+
## Editing the user config from the command line
|
|
193
|
+
|
|
194
|
+
Rather than hand-editing the file, `mooring config` reads and writes it by
|
|
195
|
+
**dotted key** (`section.subsection.key`). It touches only the user file and
|
|
196
|
+
leaves every other setting in place:
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
mooring config set ai.pii.enabled true # bool
|
|
200
|
+
mooring config set ai.pii.name_threshold 0.6 # number
|
|
201
|
+
mooring config set ai.pii.name_labels person name organization # several tokens = a list
|
|
202
|
+
mooring config get ai.pii.enabled # print the effective value
|
|
203
|
+
mooring config unset ai.pii.enabled # remove the key (revert to the default)
|
|
204
|
+
mooring config list # print the whole effective config
|
|
205
|
+
mooring config path # print the config.toml location
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Value typing is automatic: `true`/`false` become booleans, `5`/`0.6` become
|
|
209
|
+
numbers, **several tokens become a string list**, and anything else (a path or
|
|
210
|
+
model id like `urchade/gliner_multi_pii-v1`) stays a string. To force a string
|
|
211
|
+
that looks numeric, quote it as a TOML literal, e.g. `set some.key '"123"'`.
|
|
212
|
+
|
|
213
|
+
`get` and `list` show the **effective** value (packaged default merged with your
|
|
214
|
+
file); they do **not** reflect a one-run [environment-variable](#environment-variables)
|
|
215
|
+
override. This works for any key, including the `[ai]` / `[ai.pii]` settings
|
|
216
|
+
documented in [AI privacy](ai-privacy.md).
|
|
217
|
+
|
|
192
218
|
## The runtime setup form
|
|
193
219
|
|
|
194
220
|
If a build ships **without** `client_id` / `owner` / `repo`, the hub shows a
|
|
@@ -41,6 +41,13 @@ Keep both tabs side by side: chat on one, the marimo notebook on the other.
|
|
|
41
41
|
Anything you type into a cell or the chat is visible to the assistant. Refer
|
|
42
42
|
to columns by name — don't paste actual data values.
|
|
43
43
|
|
|
44
|
+
If your admin has turned on the **structured-PII scan**, a message that looks
|
|
45
|
+
like it contains a payment card, IBAN, NHS number, email, or UK NINO is **held**
|
|
46
|
+
and you'll see a **"Send anyway"** prompt — nothing is sent until you confirm.
|
|
47
|
+
It's a best-effort safety net: it won't catch names, sort codes, or account
|
|
48
|
+
numbers, so the rule still stands. See
|
|
49
|
+
[the privacy page](../admins/ai-privacy.md#structured-pii-pre-flight-scan-opt-in-best-effort).
|
|
50
|
+
|
|
44
51
|
## Team context (optional)
|
|
45
52
|
|
|
46
53
|
If your admin enables it (`[ai] context = true`), the copilot also reads a
|
|
@@ -72,4 +79,8 @@ your files parse and to catch anything sensitive *before* you share them.
|
|
|
72
79
|
choice is remembered. (Some models — and "Auto" — have no effort setting.)
|
|
73
80
|
- The assistant can read your notebook's current code and ask for a dataset's
|
|
74
81
|
schema on its own; you don't need to paste either in.
|
|
82
|
+
- It also sees the schema of dataframes already loaded in the running notebook —
|
|
83
|
+
and this is **refreshed every time you send a message**, so if you load a new
|
|
84
|
+
dataframe mid-chat, just ask your next question; there's no need to reopen the
|
|
85
|
+
chat. (It sees column names and types only, never your data values.)
|
|
75
86
|
- It writes Polars (`pl`) by default, matching mooring's bundled notebook stack.
|
|
@@ -36,6 +36,8 @@ python mooring.pyz deps remove polars
|
|
|
36
36
|
python mooring.pyz deps list
|
|
37
37
|
python mooring.pyz deps lock
|
|
38
38
|
python mooring.pyz build-requirements [-o FILE]
|
|
39
|
+
python mooring.pyz config set <key> <value...> | config get <key>
|
|
40
|
+
python mooring.pyz config unset <key> | config list | config path
|
|
39
41
|
python mooring.pyz selftest
|
|
40
42
|
python mooring.pyz version
|
|
41
43
|
```
|
|
@@ -178,6 +180,24 @@ afterwards to remove it from the team repo for everyone; a notebook you never
|
|
|
178
180
|
shared just disappears. You are asked to confirm first — pass `-y`/`--yes` to
|
|
179
181
|
skip the prompt (required when running non-interactively, e.g. from a script).
|
|
180
182
|
|
|
183
|
+
### `config`
|
|
184
|
+
|
|
185
|
+
Read and edit your user `config.toml` by **dotted key**, without hand-editing the
|
|
186
|
+
file (every other setting is preserved):
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
python mooring.pyz config set ai.pii.enabled true
|
|
190
|
+
python mooring.pyz config set ai.pii.name_labels person name organization
|
|
191
|
+
python mooring.pyz config get ai.pii.enabled # effective value
|
|
192
|
+
python mooring.pyz config unset ai.pii.enabled # revert to the default
|
|
193
|
+
python mooring.pyz config list # whole effective config
|
|
194
|
+
python mooring.pyz config path # config.toml location
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`true`/`false` become booleans and numbers are parsed; several tokens become a
|
|
198
|
+
list; anything else stays a string. See
|
|
199
|
+
[Configuration → editing from the command line](../admins/configuration.md#editing-the-user-config-from-the-command-line).
|
|
200
|
+
|
|
181
201
|
### `selftest`
|
|
182
202
|
|
|
183
203
|
Verify the bundled environment and print a diagnostic snapshot of the machine:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mooring"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.4"
|
|
4
4
|
description = "Git-free marimo notebook sharing via GitHub"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -28,6 +28,13 @@ dependencies = [
|
|
|
28
28
|
copilot = [
|
|
29
29
|
"github-copilot-sdk>=1.0.1",
|
|
30
30
|
]
|
|
31
|
+
# Optional NER name detection for the outbound-PII guard (Phase 2). GLiNER pulls
|
|
32
|
+
# torch + transformers (hundreds of MB), so it ships as an extra — without it the
|
|
33
|
+
# guard still does its stdlib structured-PII scan. Install with
|
|
34
|
+
# `pip install mooring[pii]`. Pre-fetch the model with `mooring ai pii model`.
|
|
35
|
+
pii = [
|
|
36
|
+
"gliner>=0.2.13",
|
|
37
|
+
]
|
|
31
38
|
|
|
32
39
|
[project.scripts]
|
|
33
40
|
mooring = "mooring.cli:main"
|
|
@@ -65,6 +65,12 @@ class AIProvider(Protocol):
|
|
|
65
65
|
model: str | None = None,
|
|
66
66
|
reasoning_effort: str | None = None,
|
|
67
67
|
dictionary=None,
|
|
68
|
+
pii_enabled: bool = False,
|
|
69
|
+
pii_block: bool = True,
|
|
70
|
+
pii_names: bool = False,
|
|
71
|
+
pii_name_labels: tuple[str, ...] | None = None,
|
|
72
|
+
pii_name_threshold: float = 0.7,
|
|
73
|
+
pii_name_model: str | None = None,
|
|
68
74
|
):
|
|
69
75
|
"""Open a long-lived, streaming chat session (a ``ChatBroadcaster``).
|
|
70
76
|
|