@nationaldesignstudio/rampart 0.1.1

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 (62) hide show
  1. package/LICENSE +402 -0
  2. package/MODEL_CARD.md +422 -0
  3. package/README.md +279 -0
  4. package/RELEASE.md +97 -0
  5. package/WHITEPAPER.md +316 -0
  6. package/dist/index.d.ts +23 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +35639 -0
  9. package/dist/index.js.map +36 -0
  10. package/dist/src/guard.d.ts +94 -0
  11. package/dist/src/guard.d.ts.map +1 -0
  12. package/dist/src/heuristics.d.ts +14 -0
  13. package/dist/src/heuristics.d.ts.map +1 -0
  14. package/dist/src/ner/classifier.d.ts +92 -0
  15. package/dist/src/ner/classifier.d.ts.map +1 -0
  16. package/dist/src/ner/worker.d.ts +44 -0
  17. package/dist/src/ner/worker.d.ts.map +1 -0
  18. package/dist/src/ner/worker.js +35302 -0
  19. package/dist/src/ner/worker.js.map +30 -0
  20. package/dist/src/pipeline.d.ts +76 -0
  21. package/dist/src/pipeline.d.ts.map +1 -0
  22. package/dist/src/policy.d.ts +27 -0
  23. package/dist/src/policy.d.ts.map +1 -0
  24. package/dist/src/premask.d.ts +48 -0
  25. package/dist/src/premask.d.ts.map +1 -0
  26. package/dist/src/session.d.ts +60 -0
  27. package/dist/src/session.d.ts.map +1 -0
  28. package/dist/src/streaming.d.ts +32 -0
  29. package/dist/src/streaming.d.ts.map +1 -0
  30. package/dist/src/types.d.ts +43 -0
  31. package/dist/src/types.d.ts.map +1 -0
  32. package/dist/src/validators.d.ts +16 -0
  33. package/dist/src/validators.d.ts.map +1 -0
  34. package/eval/bench/README.md +91 -0
  35. package/eval/bench/fetch.ts +152 -0
  36. package/eval/bench/labels.ts +45 -0
  37. package/eval/bench/run.ts +146 -0
  38. package/eval/bench/runs/m06-v3-30k/by_language.json +303 -0
  39. package/eval/bench/runs/m06-v3-30k/summary.json +56 -0
  40. package/eval/bench/runs/sample-900/by_language.json +303 -0
  41. package/eval/bench/runs/sample-900/manifest.json +926 -0
  42. package/eval/bench/runs/sample-900/summary.json +56 -0
  43. package/eval/bench/score.ts +197 -0
  44. package/eval/bench/webgpu/entry.ts +70 -0
  45. package/eval/bench/webgpu/index.html +12 -0
  46. package/eval/bench/webgpu.ts +209 -0
  47. package/eval/public-cases.ts +412 -0
  48. package/eval/run-public-eval.ts +140 -0
  49. package/examples/basic-chat.ts +12 -0
  50. package/examples/pii-worker.ts +3 -0
  51. package/index.ts +47 -0
  52. package/package.json +103 -0
  53. package/src/guard.ts +170 -0
  54. package/src/heuristics.ts +141 -0
  55. package/src/ner/classifier.ts +580 -0
  56. package/src/ner/worker.ts +130 -0
  57. package/src/policy.ts +64 -0
  58. package/src/premask.ts +90 -0
  59. package/src/session.ts +99 -0
  60. package/src/streaming.ts +73 -0
  61. package/src/types.ts +74 -0
  62. package/src/validators.ts +40 -0
package/README.md ADDED
@@ -0,0 +1,279 @@
1
+ # Rampart
2
+
3
+ **Rampart** is a local-first system for removing personally identifiable information
4
+ from user-typed text before it leaves the browser. It combines a 14.7 MB ONNX
5
+ token-classification model with a deterministic recognizer layer; together they form
6
+ a defense-in-depth pipeline released as a complete, reproducible artifact.
7
+
8
+ This repository ships the runtime as [`@nationaldesignstudio/rampart`](https://www.npmjs.com/package/@nationaldesignstudio/rampart).
9
+ Model weights load from Hugging Face by default; a copy also lives in `model/` for
10
+ local serving and training.
11
+
12
+ ```txt
13
+ "My name is Alex Rivera and my SSN is 472-81-0094."
14
+ → "My name is [GIVEN_NAME_1] [SURNAME_1] and my SSN is [SSN_1]."
15
+ ```
16
+
17
+ The model provider sees placeholders. The user sees restored values on the client.
18
+ The session table never leaves the device.
19
+
20
+ Rampart is **harm reduction**, not perfect protection. Personal data the client
21
+ fails to redact is the upper bound on what can leak through a model provider, a
22
+ logging pipeline, or a future infrastructure compromise. No detector at this size
23
+ catches everything; we document failure modes openly and ship regression tests so
24
+ future training runs surface drops immediately.
25
+
26
+ See [WHITEPAPER.md](./WHITEPAPER.md) for the full technical writeup — methodology,
27
+ candidate sweep, calibration, schema reconciliation, and reproducibility.
28
+
29
+ ## Supported scope
30
+
31
+ This release supports **seven Latin-script languages**: English, Spanish, French,
32
+ German, Italian, Portuguese, and Dutch. Every headline number below is measured on
33
+ these languages.
34
+
35
+ Text and names in **non-Latin scripts** (e.g. Chinese, Japanese, Korean, Arabic,
36
+ Cyrillic, Devanagari) are **out of scope in this release**: recall drops sharply and
37
+ the system should not be relied on for them. See [Limitations](#limitations).
38
+
39
+ ## Headline results
40
+
41
+ On a 30,000-row held-out test set spanning all **seven supported languages** from
42
+ the OpenPII 1.5M dataset, the **full system** (model + deterministic layer +
43
+ policy) achieves:
44
+
45
+ | Metric | Value |
46
+ | --- | ---: |
47
+ | Private-term recall (7 languages) | **98.42%** (Wilson 95% CI [98.35, 98.49]) |
48
+ | Public-term retention | 91.7% term-presence (>99% policy-aware\*) |
49
+ | Latency p50 (Node ONNX) | **6.6 ms**† |
50
+ | Shipped artifact size | **14.7 MB** Q4 ONNX (≈15.0 MB with tokenizer) |
51
+
52
+ \* OpenPII marks street-line components as public; Rampart redacts the precise
53
+ street line (`BUILDING_NUMBER` + `STREET_NAME`) and the secondary-address line
54
+ (`SECONDARY_ADDRESS`) while keeping city, state, and ZIP.
55
+ See "Schema reconciliation" in the whitepaper.
56
+
57
+ † Latency is hardware-dependent; the committed `eval/bench/runs/sample-900` proof run
58
+ records ≈14 ms p50 on CI hardware. Over the held-out slice the browser pipeline runs at
59
+ **3.9 ms p50** on WebGPU (Apple Metal) and 12.6 ms on WASM via `bun run bench:webgpu`.
60
+
61
+ These numbers come from the shipped Q4 pipeline scored end-to-end by the committed
62
+ `eval/bench` harness on a pinned held-out slice. The harness was corrected since the
63
+ previous revision: city/state/ZIP are now scored as kept (matching the runtime policy)
64
+ rather than counted as leaks, which is why public retention rose to ~90% while the
65
+ headline recall is reported against the larger, harder seven-language slice.
66
+
67
+ Per supported language, on the same 30,000-row test set:
68
+
69
+ | Language | Private recall | Public retention |
70
+ | --- | ---: | ---: |
71
+ | English (en) | 98.85% | 90.5% |
72
+ | Spanish (es) | 98.84% | 91.6% |
73
+ | French (fr) | 98.41% | 92.8% |
74
+ | German (de) | 97.94% | 91.7% |
75
+ | Italian (it) | 97.83% | 94.1% |
76
+ | Portuguese (pt) | 97.73% | 92.5% |
77
+ | Dutch (nl) | 97.21% | 91.9% |
78
+
79
+ On the English+Spanish slice the full system scores **98.85%** recall — the slice used
80
+ for the model-selection sweep in the whitepaper.
81
+
82
+ ## Design goals
83
+
84
+ 1. **Local-first privacy.** Remove personal information before it reaches application
85
+ infrastructure. Data the server never receives cannot be leaked downstream.
86
+ 2. **Browser-deployable.** Under 15 MB on the wire — small enough for a low-end phone
87
+ over a slow connection.
88
+ 3. **Recall-biased.** A miss leaks data; over-redaction is the lesser failure mode.
89
+ 4. **Domain-aware retention.** The keep-set is policy-driven so assistants retain
90
+ coarse geography — city, state, ZIP — while the precise street line is redacted,
91
+ all without retraining.
92
+
93
+ ## Architecture
94
+
95
+ Two cooperating layers run in parallel and merge their outputs. Both run entirely in
96
+ the browser.
97
+
98
+ ### Deterministic recognizer layer
99
+
100
+ Regular expressions paired with checksum and structural validators. It owns five
101
+ classes end-to-end:
102
+
103
+ - **Credit cards** — Luhn-checksummed over the digit projection, so every
104
+ separator form collapses to one rule and a 16-digit number that fails Luhn is
105
+ not redacted as a card.
106
+ - **SSNs** — structural rules reject reserved areas (`000`, `666`, `9xx`) and
107
+ ZIP+4-style false positives.
108
+ - **Email**, **URLs**, and **IP addresses** (IPv4, IPv6, and MAC) — pattern-backed,
109
+ where the structure lives in the punctuation; near-100% recall, far above the
110
+ model alone (model-only URL recall is ~5%).
111
+
112
+ This layer is synchronous and runs before the model loads; its spans are masked to
113
+ sentinels so the model never re-derives them. Names, phone numbers, account and
114
+ routing numbers, government identifiers, passports, licenses, and street-address
115
+ components carry no checksum, so they are left to the model rather than guessed at
116
+ with a regex.
117
+
118
+ ### Token-classification model
119
+
120
+ A MiniLM-L6-H384 encoder fine-tuned on a 35-label BIO head (17 entity types) covers
121
+ contextual PII the regex layer can't checksum — split names (`GIVEN_NAME`,
122
+ `SURNAME`), phone numbers, account/routing/tax numbers, government IDs, passports,
123
+ licenses, and free-form address components — across seven Latin-script languages
124
+ (en, es, fr, de, it, pt, nl). Vocabulary is trimmed to 19,730 WordPieces;
125
+ single-character pieces are retained for rare-name fallback.
126
+
127
+ Span repair (adjacent merge, bridge-and-merge, capitalized-particle rescue) lifts
128
+ span-F1 to 0.53 strict (IoU=1.0) and 0.66 relaxed (IoU≥0.5) on the headline test
129
+ set — well above the fragmented spans HuggingFace's default aggregation produces
130
+ for subword-split names.
131
+
132
+ ### Policy and session table
133
+
134
+ **Default-deny policy:** every detected label is redacted unless explicitly kept.
135
+ The default keep-set is `{CITY, STATE, ZIP_CODE}` — coarse geography an assistant
136
+ can reason about — while the precise street line (`BUILDING_NUMBER` + `STREET_NAME`)
137
+ and the secondary-address line (`SECONDARY_ADDRESS`) are always redacted.
138
+
139
+ **Session table:** maps each raw value to a stable placeholder (`Maria Garcia → [GIVEN_NAME_1] [SURNAME_1]`).
140
+ Placeholders are restored locally in assistant responses. The table is never transmitted.
141
+
142
+ The npm package exposes a single entry point — `createGuard()` returns a `ChatGuard`
143
+ that runs the full pipeline (detect → policy → placeholders) and keeps per-conversation
144
+ state for `reveal()`.
145
+
146
+ ## Install
147
+
148
+ ```bash
149
+ npm install @nationaldesignstudio/rampart @huggingface/transformers
150
+ ```
151
+
152
+ `@huggingface/transformers` is a peer dependency — your app bundles and serves it.
153
+
154
+ ## Usage
155
+
156
+ Create one `ChatGuard` per conversation. `createGuard()` loads the shipped q4 classifier
157
+ (`nationaldesignstudio/rampart`) from Hugging Face by default, caches it on-device, and
158
+ pairs it with the deterministic layer.
159
+
160
+ ```ts
161
+ import { createGuard } from "@nationaldesignstudio/rampart";
162
+
163
+ const guard = await createGuard();
164
+
165
+ // Scrub the user message before it reaches your LLM or server.
166
+ const safe = await guard.protect(userMessage);
167
+
168
+ // Send safe.text — placeholders, not raw PII — to the model.
169
+ const reply = await llm(safe.text);
170
+
171
+ // Restore real values before showing the reply to the user.
172
+ guard.reveal(reply);
173
+ ```
174
+
175
+ Streaming replies: `stream.pipeThrough(guard.revealTransform())`.
176
+
177
+ Scrub model output before logging: `await guard.protectReply(reply)`.
178
+
179
+ ### Options
180
+
181
+ | Option | Default | Purpose |
182
+ | --- | --- | --- |
183
+ | `model` | `nationaldesignstudio/rampart` | Hugging Face model id or local ONNX directory |
184
+ | `device` | `"wasm"` | `"wasm"` / `"webgpu"` in browsers; `"cpu"` in Node |
185
+ | `worker` | — | Worker script URL — run NER off the main thread |
186
+ | `heuristicsOnly` | `false` | Skip the classifier; structured PII only |
187
+ | `keepLabels` | city, state, ZIP | Widen or narrow the default-deny keep-set |
188
+ | `aliases` | `{}` | Display names for tokens, e.g. `{ GIVEN_NAME: "NAME" }` |
189
+ | `ner` | — | Inject a custom detector; skips `model` |
190
+ | `minScore` | `0.4` | Drop model spans below this confidence |
191
+ | `noPrefilter` | `false` | Feed raw text to the model (no-prefilter ablation); heuristics still run |
192
+
193
+ ```ts
194
+ // Local weights (e.g. self-hosted or repo `model/` dir)
195
+ const guard = await createGuard({ model: "./model", device: "cpu" });
196
+
197
+ // Heuristics only — no model load
198
+ const guard = await createGuard({ heuristicsOnly: true });
199
+
200
+ // Keep inference off the UI thread (browser)
201
+ const guard = await createGuard({
202
+ worker: new URL("./pii-worker.ts", import.meta.url),
203
+ });
204
+ ```
205
+
206
+ Custom models must be token-classification ONNX exports (q4) with a label schema
207
+ compatible with Rampart. Dtype is fixed to q4.
208
+
209
+ Set `HF_TOKEN` when pulling from a private Hugging Face repo.
210
+
211
+ ### CLI
212
+
213
+ ```bash
214
+ bun run redact # interactive terminal redactor (Node, device: cpu)
215
+ ```
216
+
217
+ ## Limitations
218
+
219
+ The most consequential documented gaps:
220
+
221
+ - **Non-Latin scripts are out of scope.** This release supports the seven
222
+ Latin-script languages above only. On the fairness suite, Latin-script names —
223
+ including diacritics — recall ~99.8%, but names in non-Latin scripts recall ~14%
224
+ in aggregate (Russian 2%, Arabic 5%, Hindi 6%, Han Chinese 9%, Korean 15%,
225
+ Japanese 46%). There is no checksum for names, so this gap surfaces at the system
226
+ level. **Do not deploy this release for populations who routinely type non-Latin-script
227
+ names without compensating controls.** Tracked by a stratified regression test in
228
+ the eval suite; closing it is the top priority for the next training cycle.
229
+ - **Adversarial robustness.** 86.4% on a 20-case hostile-input suite. Combined
230
+ attacks can still bypass both layers. The threat model is good-faith user entry,
231
+ not a motivated adversary smuggling PII past their own filter.
232
+ - **Indirect identifiers.** Rare condition + ZIP-style inferential leaks are out of scope.
233
+ - **Non-text inputs.** Images, audio, and structured form fields are not supported.
234
+
235
+ See [MODEL_CARD.md](./MODEL_CARD.md) for per-class statistics and failure modes.
236
+
237
+ ## Evaluation
238
+
239
+ Everything is evaluated in TypeScript against the shipped `ChatGuard` pipeline — the
240
+ same code consumers run is the code under test:
241
+
242
+ - **Unit tests** — deterministic detectors, redaction policy, streaming rehydration,
243
+ span repair.
244
+ - **Public API end-to-end suite** — chat-style cases across structured identifiers,
245
+ names from multiple traditions, addresses, government IDs, keep-set behavior, and
246
+ no-PII controls.
247
+ - **Native benchmark** ([`eval/bench`](./eval/bench)) — runs the real
248
+ `@nationaldesignstudio/rampart` pipeline over a frozen OpenPII held-out slice and
249
+ scores recall/retention (Wilson CI), span-F1, and latency. The headline numbers
250
+ above are regenerated by this harness.
251
+
252
+ ```bash
253
+ bun test # unit + public API suites
254
+ bun run eval:public # end-to-end chat cases
255
+
256
+ bun run bench:fetch --n 30000 # materialise the held-out slice
257
+ bun run bench # score the shipped pipeline
258
+ ```
259
+
260
+ ## Documentation
261
+
262
+ | Document | Contents |
263
+ | --- | --- |
264
+ | [WHITEPAPER.md](./WHITEPAPER.md) | Full technical writeup |
265
+ | [MODEL_CARD.md](./MODEL_CARD.md) | Model summary, training data, eval results, limitations |
266
+ | [RELEASE.md](./RELEASE.md) | Verify and publish checklist |
267
+
268
+ ## Distribution
269
+
270
+ | Channel | Artifact |
271
+ | --- | --- |
272
+ | **npm** | `@nationaldesignstudio/rampart` — TypeScript runtime API |
273
+ | **GitHub** | [`nationaldesignstudio/rampart`](https://github.com/nationaldesignstudio/rampart) — source, tests, eval harness, model weights |
274
+
275
+ ## License
276
+
277
+ Released under [CC BY 4.0](./LICENSE) (Creative Commons Attribution 4.0 International).
278
+
279
+ Training data: OpenPII 1.5M (CC BY 4.0).
package/RELEASE.md ADDED
@@ -0,0 +1,97 @@
1
+ # Release
2
+
3
+ ## Verify
4
+
5
+ ```bash
6
+ bun run build
7
+ bun test
8
+ bun run type-check
9
+ bun run eval:public:strict
10
+ bun run export:huggingface:verify
11
+ npm pack --dry-run --cache /tmp/npm-cache-pii-filter
12
+ ```
13
+
14
+ Equivalent single command:
15
+
16
+ ```bash
17
+ bun run verify:public
18
+ ```
19
+
20
+ `eval:public:strict` enforces minimum thresholds (private recall ≥ 95%, public
21
+ retention ≥ 95%) on the public chat-case suite rather than requiring zero leaks;
22
+ it fails on a regression below either floor. The thresholds are pinned in
23
+ `eval/run-public-eval.ts`. The headline OpenPII numbers come from `eval/bench`.
24
+
25
+ ## npm
26
+
27
+ ```bash
28
+ npm publish --access restricted
29
+ ```
30
+
31
+ The package publishes compiled ESM from `dist/`, TypeScript declarations, source,
32
+ examples, eval fixtures, docs, and the browser model files.
33
+
34
+ ## Hugging Face
35
+
36
+ Target repo: `nationaldesignstudio/rampart` (private).
37
+
38
+ With `hf` authenticated:
39
+
40
+ ```bash
41
+ hf repos create nationaldesignstudio/rampart --type model --private
42
+ bun run publish:huggingface
43
+ ```
44
+
45
+ Manual Git LFS flow:
46
+
47
+ ```bash
48
+ bun run export:huggingface:verify
49
+ cd hf-export
50
+ git init
51
+ git lfs install
52
+ git add .
53
+ git commit -m "Add Rampart model"
54
+ git remote add origin https://huggingface.co/nationaldesignstudio/rampart
55
+ git push origin main
56
+ ```
57
+
58
+ The export contains:
59
+
60
+ - `README.md` with Hugging Face model metadata.
61
+ - ONNX model and tokenizer files.
62
+ - `.gitattributes` for ONNX Git LFS tracking.
63
+ - `LICENSE`, `MODEL_CARD.md`, `WHITEPAPER.md`, and `examples/basic-chat.ts`.
64
+
65
+ ## Automated release (CI)
66
+
67
+ Publishing both channels is wired into GitHub Actions
68
+ (`.github/workflows/release.yml`). To cut a release:
69
+
70
+ 1. Bump `version` in `package.json` and land it on `main`.
71
+ 2. Publish a GitHub Release whose tag matches that version.
72
+
73
+ The workflow re-runs `verify:public` + an `npm pack` dry run as a shared gate,
74
+ then publishes to npm (`npm publish --access restricted`) and Hugging Face
75
+ (`export:huggingface:verify` → `hf upload`) as independent jobs. `npm` rejects
76
+ republishing an existing version, so the tag/release version is what ships.
77
+
78
+ Run it manually with `workflow_dispatch` to rehearse — it defaults to a dry run
79
+ (builds and verifies, publishes nothing).
80
+
81
+ Required repository secrets:
82
+
83
+ | Secret | Used by | Purpose |
84
+ | --- | --- | --- |
85
+ | `NPM_TOKEN` | npm job | npm automation token with publish rights to `@nationaldesignstudio` |
86
+ | `HF_TOKEN` | Hugging Face job | Hugging Face write token for `nationaldesignstudio/rampart` |
87
+ | `ANTHROPIC_API_KEY` | Claude review/assistant workflows | Powers `claude-code-review.yml` and `claude.yml` |
88
+
89
+ The manual `npm publish` / Git-LFS flows above remain the fallback if you ever
90
+ need to publish from a workstation.
91
+
92
+ ## GitHub
93
+
94
+ This repository is the standalone open-source home for Rampart. The GitHub
95
+ workflow (`.github/workflows/ci.yml`) runs `verify:public` and an npm pack dry
96
+ run on every push and pull request. `.github/workflows/release.yml` ships to npm
97
+ and Hugging Face on a published release.