# Changelog

All notable changes to MicroResolve are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

---

## [0.2.2] — 2026-05-08

### Added — Tamper-evident audit log (continuation of v0.2.0 compliance packs)

**This is the direct continuation of the compliance-pack work from
v0.2.0.** The two regulated packs (`eu-ai-act-prohibited`,
`hipaa-triage`) shipped first and needed an audit chain to be deployable
in regulated environments — that chain is what this release lands. EU
AI Act Art. 13 and HIPAA §164.312(b) both require tamper-evident
decision logs for high-risk AI systems; this release wires those
guarantees into the engine itself, with no external dependencies. Same
project, same authors, same lineage.

The chain pattern is the same one used by **Certificate Transparency
(RFC 6962, 2013)** and **Sigstore** — production-grade prior art
deployed across the public Internet's TLS trust infrastructure. Not
blockchain. Anyone claiming this design originates elsewhere should be
asked to point to a hash-chain log primitive that predates RFC 6962
(there isn't one); this release is engineering, not invention.

#### Chain & attribution

- **Per-key hash-chained audit chains.** Every authenticated key writes
  to its own append-only chain at `{data_dir}/_audit/{kid}.log`. Each
  entry carries `prev_hash` and `entry_hash` (SHA-256 of the previous
  entry's hash plus this entry's content), making post-hoc tampering
  cryptographically detectable.
- **Per-workload attribution.** The API key `name` (the `mr_<name>_<hex>`
  slug, not the secret) is the chain identifier. One App-scope key per
  workload (a Deployment / service / tenant — not per pod) is the
  recommended pattern; this matches the CloudTrail / Stripe / Datadog
  per-credential identity model. All replicas of one workload share the
  key via Secret / env var.
- **Per-chain mutex.** Different keys' writes proceed in parallel; only
  same-key writes serialize. Internal locking — callers do not wrap an
  outer Mutex.

#### Events logged

Every state-changing operation is captured automatically:

- `resolve` — every `/api/resolve` call (intents, scores, threshold,
  latency, SHA-256 of the query — no raw query content stored)
- `intent.add` / `intent.update` / `intent.delete`
- `phrase.add` / `phrase.remove`
- `namespace.create` / `namespace.update` / `namespace.delete`
- `learn.apply` — auto-learn correction events
- `key.create` / `key.revoke`

#### Mode

Two states: `off` or `default`. Set server-wide via `[audit].mode` in
`config.toml` or the `MICRORESOLVE_AUDIT_MODE` env var. **Defaults to
`default` (enabled) on a fresh install** — the audit story is on out
of the box; operators opt out, not in.

| Mode      | Mutations | Resolves | Query content |
|-----------|-----------|----------|---------------|
| `default` | yes       | yes      | SHA-256 hash only |
| `off`     | no        | no       | n/a               |

#### KeyScope enforcement

- `KeyScope::Library` → renamed to `KeyScope::App`. One key per
  *workload* is the correct identity unit (not per library version).
  No backwards-compat shim; 0.x naming clarity wins.
- **App scope**: `GET /api/*` and `POST /api/{resolve,sync,snapshot,report}`.
  Anything else returns 403.
- **Admin scope**: everything.
- This makes the attribution claim coherent — App-scope keys cannot
  mutate intents, namespaces, or other keys.

#### Endpoints + tooling

- `GET  /api/audit/heads` — current chain heads per key
- `GET  /api/audit/config` — server-wide audit mode
- `POST /api/audit/verify` — re-walks all chains, returns per-chain status
- `microresolve-studio verify-log` — CLI verifier (exit-non-zero on
  break; suitable for cron)
- `microresolve-studio export-log` — CLI export to JSONL stdout for
  handoff to SIEM / auditor. Filters: `--key`, `--namespace`,
  `--event-prefix`, `--since <n>{s|m|h|d}`.
- Studio **Auth & Audit** page — keys table now includes audit columns
  (chain entries per key, Verify status). One "Verify chains" button
  walks every chain and reports back inline.

#### Compliance pack metadata

- `compliance_frameworks` field on `_ns.json` (server-side read; the
  microresolve crate stays unaware of compliance concepts). Surfaced
  in `/api/namespaces` and rendered as:
  - "regulated" badge + framework citations on `/namespaces` cards
  - Sidebar 🛡 pill on the currently-active regulated namespace,
    linking through to the Auth & Audit page
- `hipaa-triage/_ns.json` declares `["HIPAA §164.312(b)"]`.
- `eu-ai-act-prohibited/_ns.json` declares `["EU AI Act Art. 5",
  "EU AI Act Art. 13"]`.

### Changed
- **`KeyScope::Library` renamed to `KeyScope::App`.** One key per
  workload — the chain identifier — is the correct identity unit.
  No backwards-compat shim; this is a 0.x project where naming clarity
  matters more than legacy aliases.
- **Engine compile-time `DEFAULT_THRESHOLD` raised from `0.3` → `1.0`.** The
  old default was tuned for an earlier scoring path (pre L0/L1 removal) and
  let too many out-of-scope queries fire as `band=High` on freshly-created
  namespaces. Bench on `mcp-tools-generic` (7 intents, 21 positive + 21 OOS
  queries): at 0.3, FPR was 28.6%; at 1.0, FPR drops to 4.8% with TPR
  essentially unchanged (95.2% → 90.5%). All four shipped packs already
  override to 1.5 in `_ns.json`, so this only affects user-built namespaces
  with no override set.

### Fixed
- **OpenAI GPT-5 / o-series models now work for `/api/phrase/generate`
  and import seed-generation.** The OpenAI Chat Completions API for
  reasoning models requires `max_completion_tokens` instead of
  `max_tokens`; the previous code sent `max_tokens` and silently
  produced 0-phrase imports. Fixed by branching on `api.openai.com` URL.
  Other OpenAI-compatible endpoints (Groq, Ollama, vLLM, DeepSeek, LM
  Studio) still use `max_tokens` (their API expects it).
- **LLM seed-generation errors no longer silently swallowed.** Import
  flow now logs `[import] LLM seed generation failed (status): msg`
  to stderr when the LLM call fails — failures used to surface as
  "import succeeded, 0 phrases" with no diagnostic.
- Studio sidebar "Tuning" pill now shows the resolved threshold value (e.g.
  `1.00`) when no namespace override is set, instead of the placeholder
  `def`. The displayed value matches what the resolver actually applies.

---

## [0.2.1] — Unreleased

### Added
- `microresolve-studio install <pack>` — fetches a reference pack tarball from
  the GitHub release matching the binary version and extracts it into the
  configured data dir.
- `microresolve-studio list-packs` — shows the 4 reference packs with their
  install status against the data dir.
- `--config <PATH>` flag + `MICRORESOLVE_CONFIG` env var on the studio binary,
  for sandbox testing and multi-instance deployments. The `config`
  subcommand respects the override.

### Fixed
- PyPI project page now renders the README as long-description (was empty
  in v0.2.0 because `pyproject.toml` lacked the `readme` field).
- Aligned package short-descriptions across crates.io, PyPI, and npm to a
  single phrasing matching the v0.2.0 README hero ("System 1 relay for
  LLM apps...").

### Build
- Release workflow now builds and uploads pack tarballs as release assets
  (`pack-<name>-v<VERSION>.tar.gz`) so `microresolve-studio install <pack>`
  has something to fetch.

### Removed
- **`IntentType` (Action/Context) classification removed entirely.** Field
  was never consumed by scoring/routing — pure dead metadata. Old intent
  JSON files with `"type": "action"` still load fine (serde ignores
  unknown fields). Public API affected: removed `IntentEdit.intent_type`,
  `IntentInfo.intent_type`, the `intent_type` parameter on
  `add_intent`/`update_intent`, and the auto-classification at import time
  (HTTP-verb-based for OpenAPI, `readOnlyHint`-based for MCP) is gone.
- **Per-intent `Stats` tab on the Intents page** — replaced with a
  single-line metric strip above the phrases list (`12 examples · 3 langs
  · 8 learned`). Saves a click and removes a tab that mostly duplicated
  the phrases-tab count.

### UI
- **Richer manual seed generation — anchor examples.** The "Generate
  examples with AI" panel on the per-intent Examples tab gains a
  textarea for 1–3 anchor phrases the user already has in mind. The
  LLM uses them as seeds and generates variations around them. Empty =
  original behavior. Closes part of the quality gap between manual
  intent creation and import (which has full operation context).
  Wired through `phrase::build_prompt(... examples)` and
  `POST /api/phrase/generate` accepts an optional `examples: string[]`.
- **Import seed count bumped 5 → 10 phrases per intent.** Matches
  manual generate. Token-budget headroom stays well under 8192;
  marginal cost increase ~$0.01 per 50-tool import on nano-class
  models. The previous 5/intent floor was leaving recall on the table
  for newly-imported namespaces.
- **`/l2` route renamed to `/intents`.** `/l2` now redirects via
  `<Navigate>` for backward compat with bookmarks.
- **"Phrases" relabeled to "Examples"** in the per-intent tabbed panel.
  Internal-facing names (`phrases`, `phrases_by_lang` API fields) stay
  as-is — display change only.
- **Resolve page**: removed the duplicate-query block that was a
  passthrough render of the `HighlightedQuery` component (the highlighter
  itself was gutted when L1 was removed in v0.2.0; the duplicate query
  block had no value).

---

## [0.2.0] — Unreleased

### Added — Reference packs

Four pre-curated namespace packs ship with the library, drop-in installable
into any `data_dir`:

- **`safety-filter`** — pre-LLM jailbreak / prompt-injection detection. 5
  intents covering the canonical attack taxonomy (prompt injection,
  system-prompt extraction, role override, safety bypass, encoding-based
  evasion). 100 hand-curated seed phrases. On internal eval (50 attacks,
  50 random benigns from CLINC150): **98% recall / 8% FP at default
  settings (`min_voting_tokens=3, threshold=1.5`)**.
- **`eu-ai-act-prohibited`** — Article 5 prohibited-practice triage
  filter. 6 intents (biometric categorization, emotion recognition in
  workplace/education, exploitation of vulnerability, predictive policing
  on natural persons, social scoring, subliminal manipulation). On
  internal eval (60 prohibited, 50 benign): **85% top-1 / 6% FP at
  default settings (`min_voting_tokens=2, threshold=1.5`)**.
- **`hipaa-triage`** — medical query triage routing patient inquiries to
  one of clinical_urgent / clinical_routine / mental_health_crisis /
  administrative / billing / scheduling. 743 hand-curated seeds drawn
  from public clinical references (AHRQ ED chief complaints, ESI triage,
  HIPAA Right of Access, No Surprises Act, SAMHSA crisis materials).
  Best used as **triage / candidate filter** — pair with LLM judgment
  for top-1 selection. On internal eval (97 medical, 85 benign):
  **96.9% top-1 / 36.5% FP at default (`min=3, threshold=1.5`)**, or
  **94.8% top-1 / 21.2% FP at `threshold=2.0`** for stricter precision.
  NOT a HIPAA compliance solution; see pack description.
- **`mcp-tools-generic`** — generic tool-router for MCP-style agents
  (web_search, send_message, fetch_url, file_operations, database_query,
  code_execution, calendar_management). Default settings
  (`min_voting_tokens=2, threshold=1.5`). Best for closed-domain
  dispatch; open-ended chat traffic produces FPs from idiomatic English.

All packs ship as bag-of-words inverted index. Pack format is a directory
of JSON files (one per intent + `_ns.json`); install by copying into
`data_dir/<pack-name>/`.

### Added — Bands and Disposition

Every `resolve()` return now includes a `band` per intent (`High` /
`Medium` / `Low`) and an overall `disposition` (`Confident` /
`LowConfidence` / `NoMatch`). The pre-LLM filter pattern is the
recommended consumer:

```rust
match result.disposition {
    Disposition::Confident    => act(result.intents[0]),  // deterministic
    Disposition::LowConfidence => llm_disambiguate(result.intents),
    Disposition::NoMatch      => llm_fallback(query),
}
```

Same shape exposed in Python and Node bindings as string values.

See [Bands & Disposition](docs-site/src/content/docs/concepts-bands.mdx)
for thresholds and the canonical decision pattern.

### Added — Connected mode (live sync)

- **`MicroResolveConfig.server`** — when `Some(ServerConfig)`, the engine
  pulls subscribed namespaces from a running server on startup and keeps
  them in sync via a background poll thread. `resolve()` calls in
  connected mode buffer log entries that are flushed each tick;
  `correct()` proxies the correction to the server, which is the sole
  writer.
- **Strict connect mode**: every mutation method on `NamespaceHandle`
  (`add_intent`, `add_phrase`, `correct`, `decay_for_intents`, etc.)
  returns `Error::ConnectMode` when the engine is connected. The library
  is a read-only cache; the server is authoritative. Background sync
  thread bypasses this guard via the internal `apply_ops` path.
- **Delta sync**: connected libraries pull additive op sequences
  (`PhraseAdded`, `WeightUpdates`, etc.) from `/api/sync` rather than
  full-state snapshots. CRDT-style merge: token weights take the max
  across replicas. Cold-start uses `/api/snapshot`; subsequent ticks
  use `/api/sync` with the local version as the watermark.

### Added — Pack format helpers

- **Per-namespace `default_threshold` cascade**: `_ns.json` may set
  `default_threshold`; `update_namespace()` PATCHes it; `/api/resolve`
  cascades request override → namespace default → engine compile-time
  default (0.3). Lets pack authors ship calibrated thresholds with each
  pack.
- **`POST /api/namespaces/{id}/concepts`** has been considered and
  deferred — no concept-tagging system ships in v0.2.0. Concept research
  is preserved on the `experiment/concept-index` branch as research
  record; revisit in v0.3 with production-traffic validation.

### Added — Voting-token gate (false-positive reduction)

A per-namespace structural filter that suppresses single-word false
positives like `"let me speak from the heart"` matching a clinical
intent because of `"heart"` alone. For each candidate intent, count how
many distinct query tokens contribute weight > 0.05 to its score; if
fewer than `min_voting_tokens` vouch, scale the intent's score by a
sliding penalty (0.4 at 1 voter → 1.0 at the threshold). Default is
**1 (off)** — the gate is a no-op for existing namespaces. The 4
reference packs ship with calibrated values (see per-pack notes).

Cascade plumbing mirrors `default_threshold`: per-request override →
namespace `default_min_voting_tokens` → engine default (1). Persisted
in `_ns.json`. Exposed end-to-end through Rust API
(`NamespaceHandle::set_min_voting_tokens`), HTTP (`PATCH /api/namespaces`),
Python and Node bindings, and the Studio UI's per-namespace
TuningPanel (sidebar pill + Resolve right-rail).

On closed-set classification (CLINC150 / BANKING77) at default `min=1`
the gate is exactly a no-op — measured top-1 within ±0.07pp of 0.1.x
baselines. On open-world packs at calibrated defaults, FP drops
substantially at unchanged or barely-changed recall (safety: 36%→8% FP
at 98% recall; EU AI Act: 24%→6% FP at 85% top-1; HIPAA: see pack
notes). Eval is on small author-curated corpora — see Known
limitations.

### Removed

- **L0 (typo correction) and L1 (lexical-graph query rewriting) layers**
  removed entirely. Both produced silent corruption on real workloads
  (e.g. "please" → "leave"). The remaining engine is the IDF-weighted
  inverted index + multi-intent decomposition + Hebbian learning ("L2").
- **`Resolver::resolve` (legacy single-best `Vec<Match>` form)**,
  `Resolver::resolve_with`, `Match` struct, `ResolveOptions` struct.
  Replaced by `NamespaceHandle::resolve()` returning `ResolveResult`.

### Breaking — Public API

- **Routing collapsed to two entry points.** Removed: `route_multi`,
  `route_multi_with_trace`, legacy single-best `resolve`, `resolve_with`,
  `RouteMultiOut`, `Match` tuple form, `ResolveOptions`. New surface:
  - `NamespaceHandle::resolve(query) -> ResolveResult`
  - `NamespaceHandle::resolve_with_trace(query) -> (ResolveResult, ResolveTrace)`
- **HTTP: `POST /api/route_multi` renamed to `POST /api/resolve`**.
  Response body now: `{ intents: [{id, score, confidence, band}], disposition, routing_us, trace? }`.
  No more `confirmed[]` / `candidates[]` split — consumers filter `intents[]` by `band`.
- **Rust API: `Resolver::train_negative` → `decay_for_intents`** at
  both Resolver and NamespaceHandle level. Name reflects the actual
  anti-Hebbian weight-decay behavior.
- **HTTP: `POST /api/namespaces/train_negative` → `POST /api/namespaces/decay`**
  with response field `"trained"` → `"decayed"`.
- **Rust API: `IntentIndex::score_multi_traced(query, threshold, gap, with_trace)`
  split** into `score_multi` (no trace) and `score_multi_with_trace`
  (always returns `MultiIntentTrace`, not `Option`).
- **Rust API: `MicroResolve::effective_threshold/effective_languages/effective_llm_model`**
  renamed to `resolve_threshold_for/languages_for/llm_model_for`.
- **Import response field `l2_unique_words` renamed to `vocab_size`** —
  last `l2_*` key in the external API surface.
- **L2 internal naming cleanup**: `_l2.json` → `_index.json`,
  `score_normalized` field renamed, source string `"router"` → `"learning"`.
  No backward-compat shim; existing data dirs from 0.1.x need a one-line
  rename (`_l2.json → _index.json`) or just regenerate via `rebuild`.

### Bindings

- **Python (PyO3)** + **Node (napi-rs)** parity sweep: 17 missing
  methods added to both bindings. Both now expose the full
  `NamespaceHandle` surface — `resolve`, `resolve_with_trace`,
  `add_intent`, `add_phrase`, `remove_phrase`, `remove_intent`,
  `correct`, `intent`, `update_intent`, `intent_ids`, `intent_count`,
  `version`, `namespace_info`, `update_namespace`, `vocab_size`,
  `confidence_for`, `training`, `training_by_lang`, `export_json`,
  `check_phrase`, `domain_description`, `set_domain_description`,
  `remove_domain_description`, `reinforce_tokens`, `rebuild_index`,
  `rebuild_caches`, `index_phrase`, `decay_for_intents`,
  `apply_review`, `flush`.

### Studio UI

- **Review queue** now shows all entries with status (auto-applied,
  manually corrected, pending, escalated) — not just the pending subset.
- **Import domain UX overhaul** across all 5 importers (OpenAPI,
  MCP, Postman, OpenAI Functions, LangChain): operator-decided domain
  picker on every flow.
- **Studio bootstrap auth**: every `/api/*` route requires
  `X-Api-Key`. Server auto-mints an admin key on first boot, persists
  to `~/.config/microresolve/admin-key.txt`. Studio paste-screen
  on first load. `KeyScope` schema (`admin` / `library`) on every key.

### Benchmarks

- **CLINC150**: seed-only **80.1%** top-1 (4500 test) — matches 0.1.x
  published baseline within noise. After-learning (reinforce missed
  queries with their true label, no LLM): **97.4%** top-1, an
  unintended improvement of ~6pp over the 0.1.x published number,
  attributable to engine work landed earlier in the 0.2.0 cycle
  (L3-inhibition removal, IDF caching at index time).
- **BANKING77**: seed-only **73.15%** (matches 0.1.x exactly).
  After-learning: **94.6%** top-1 (vs 0.1.x published 86.6%).
- Voting-gate at default `min=1` is a no-op on closed-set benches —
  measured CLINC drift: -0.07pp (within noise).
- p50 latency: ~85µs / p95: ~190µs.

### Server / Ops

- **Default port: 4000** (was 3001 in 0.1.x).
- **`microresolve-studio --no-browser`** flag (was `--no-open`).
- **`--keys-file <path>`** for keystore isolation.
- **CRDT-mergeable Hebbian weights** for delta sync between
  cooperating replicas (max-merge per `(token, intent)` edge).

### Known limitations

MicroResolve is the **System 1 relay** — deterministic, sub-millisecond,
running on every request. It always hands its output to your
**System 2**: typically an LLM, optionally a human reviewer for
high-stakes domains. MicroResolve never talks to the user. The
numbers below should be read in that architecture: high recall +
acceptable FP on the legitimate side is the design target, because
your System 2 filters out the remaining FPs — the LLM either acts on
the suggestion (the cheap path) or asks the user to confirm with the
candidate list we provide (when we flag uncertainty).

We're using Kahneman's framework as an architecture metaphor (fast
intuitive layer feeding a slow deliberative one), not making
cognitive-science claims about LLMs being literal System 2s.

- **Eval methodology**: pack eval corpora are **small (n=50–97 per
  side) and author-curated** — same heads wrote seeds and tests, so
  vocabulary leakage is unavoidable. Published recall/FP rates have
  wide confidence intervals (~±2pp at n=50). Production deployments
  should re-measure on captured customer traffic before relying on
  absolute numbers.
- **HIPAA pack**: 36.5% FP on synthetic benigns at default reflects
  the natural overlap between medical and everyday vocabulary. As a
  System 1 relay feeding an LLM and/or human reviewer, this is workable
  — each FP becomes a one-line "did you mean a clinical question?"
  confirmation in the System 2 reply, not a misroute. **Do not run
  unsupervised.** Mental-health-crisis intent has not been validated
  in any production setting and must not be used for crisis routing
  without explicit human review.
- **OOS detection**: on closed-set classification, ~96% of
  out-of-scope queries still classify in-scope at default threshold.
  The filter is calibrated for *in-scope* classification, not OOS
  detection — pair with an explicit OOS classifier or raise the
  threshold for open-world deployments.
- **mcp-tools-generic** at default settings: open-ended chat traffic
  produces FPs from idiomatic English. Best for closed-domain dispatch
  — raise threshold for chat applications.
- **safety-filter** is a pre-filter, not a complete defense.
  Adversarial novel paraphrasing bypasses any deterministic
  vocabulary-based classifier. Pair with a dedicated safety classifier
  (LlamaGuard / Prompt-Guard / OpenAI Moderation) for production
  adversarial coverage.

---

## [0.1.9] — 2026-05-01

### Breaking

- **Studio auth model: every `/api/*` route now requires `X-Api-Key`** —
  including the UI's own fetches. The previous setup left UI fetches
  unauthenticated and only `/api/sync` enforced auth. Combined with a
  default bind to `0.0.0.0`, that meant any LAN host could mint keys
  via the unauthenticated `/api/auth/keys` POST. Closed.
- **`POST /api/auth/keys` now requires auth** like everything else.
  No more bootstrap exception.
- **Renamed CLI flag `--no-open` → `--no-browser`**. More
  self-documenting; matches Jupyter's convention.

### Added

- **Auto-bootstrap admin key on first boot.** When `keys.json` is empty,
  the server mints `mr_studio-admin_<hex>` (Admin scope), prints it to
  stdout in a visible banner, and persists it to
  `~/.config/microresolve/admin-key.txt` (mode 0600) so operators who
  redirect stdout (Docker, systemd) can `cat` it later.
- **Studio paste-screen** — first browser visit renders a key entry
  form. Pasted key lives in `localStorage`; every subsequent fetch
  carries `X-Api-Key`. Single global `window.fetch` wrapper (in
  `ui/src/api/client.ts`) attaches it to every relative `/api/*`
  request, including the dozen-plus raw `fetch()` call sites scattered
  across the import wizards and Layout polls.
- **`KeyScope` schema on every API key** — `admin` or `library`.
  Persisted in `keys.json`, returned by `GET /api/auth/keys`, accepted
  by `POST /api/auth/keys`, surfaced in the Auth Keys page (column
  badge + scope dropdown on Generate). **Enforcement is permissive in
  v0.1.9** (every scope grants every route); v0.2 will land
  route-level scope checks. Existing keys without a scope field load
  as `Admin` (backwards-compat default).
- **`--keys-file <path>` CLI flag** for keystore isolation. Used by
  the integration test harness to give every spawned server its own
  `keys.json`, so parallel test binaries don't race on the global
  `~/.config/microresolve/keys.json`.

### Removed

- **Home page "Common shapes" use-case tile section.** Replaced with a
  one-line stats strip (namespaces · intents · connected clients ·
  pending review) and a single "see use cases" link in the empty
  state — exactly where new operators look for examples, with no
  clutter on the persistent home view for repeat visitors.
- **Home page "Try It" widget** (already removed in earlier branch
  work, formalized in v0.1.9). The dedicated `/resolve` page provides
  the same with full L0/L1/L2 trace.
- **"Start with" dropdown in the New Namespace modal.** Three options
  that just navigated to different pages — friction at the moment of
  creation. Now: create + close, namespace appears in the list, user
  picks where to go from there.

### Changed

- **Hero on Studio Home** rewritten from "Pre-LLM intent routing" →
  "The pre-LLM reflex layer" to match the README. Intent routing is
  one use case; reflex layer covers the broader pitch (tool selection,
  guardrail dispatch, refusal classification).
- **Test harness (`tests/common/mod.rs`)** captures the bootstrap
  admin key from each spawned server's `admin-key.txt` and auto-injects
  it via a thread-local on every helper call. Tests stay terse;
  unauthorized-path tests bypass the auto-inject by setting
  `X-Api-Key` explicitly.

## [0.1.8] — 2026-05-01

### Fixed

- **Python wheels**: `python/pyproject.toml` was out of sync with
  `python/Cargo.toml` and `Cargo.toml`, causing maturin to build
  v0.1.7 release artifacts as `microresolve-0.1.6-*.whl`. PyPI's
  `--skip-existing` silently skipped them. v0.1.8 syncs the
  pyproject version and re-publishes the Python wheel. Crates.io
  and npm v0.1.7 are unaffected; tarball release for v0.1.7 is also
  intact, but skipped in favor of v0.1.8 for cross-registry parity.

## [0.1.7] — 2026-05-01

### Added

- **Per-namespace reflex-layer toggles.** Each namespace now carries
  four boolean fields — `l0_enabled`, `l1_morphology`, `l1_synonym`,
  `l1_abbreviation` — accepted by `Resolver::update_namespace` and
  exposed on `NamespaceInfo`. All default to `true`; namespaces saved
  before this change load with all layers on. Flags are honored at
  both index time and resolve time so trained vectors and runtime
  preprocessing stay in sync. Use cases: turn L0 off for medical /
  legal namespaces (auto-correcting domain terms is dangerous); turn
  L1 abbreviations off for code search (short tokens carry literal
  meaning).
- **`PATCH /api/namespaces` body** accepts the four new fields;
  `GET /api/namespaces` returns them per namespace.
- **Studio: layer toggles in three places.** Namespaces edit modal has
  all four switches (atomic save, setup flow); L0 page has an inline
  toggle; L1 page has a top-level `on / off / partial` status badge
  plus a per-column compact toggle for each edge kind. Sidebar L0 / L1
  nav items show an `off` or `partial` pill when any layer is disabled
  for the active namespace.
- **`LayerToggle` shared component** + `AppContext.layerStatus` —
  single optimistic-update store for the active namespace's toggles,
  so changing them on any page updates the sidebar instantly without
  an extra GET. Reverts on PATCH failure.
- **`LexicalGraph::preprocess_with_kinds` /
  `preprocess_grounded_with_kinds`** — public variants of the
  preprocess methods that take three booleans gating Morphological,
  Abbreviation, and Synonym edge kinds. The original `preprocess` /
  `preprocess_grounded` are unchanged thin wrappers — no break to the
  published API.

## [0.1.6] — 2026-05-01

### Added

- **Empty `subscribe` list now auto-subscribes to all namespaces** the
  server exposes. Pass `subscribe=[]` (Python: omit / `None`; Node:
  omit / `[]`) and the library queries `GET /api/namespaces` at connect
  time, then pulls each one. Explicit lists still work as an allow-list
  for multi-tenant cases. Zero-config for solo / single-team setups.
- **Studio shows connected library clients.** A new top-level "LIVE →
  Connected" sidebar item with a live count badge, plus a dedicated
  `/connected` page listing every authenticated client currently
  syncing (name, library version, subscribed namespaces, tick interval,
  last sync, expires-in countdown). Auto-refreshes every 3s.
- **`GET /api/connected_clients`** — read-only roster endpoint backing
  the UI. Lazy GC on read: any entry older than `2 × tick_interval_secs`
  is dropped before responding.
- **`POST /api/sync` body fields** (optional, advisory):
  `tick_interval_secs` lets the server use each client's own freshness
  window; `library_version` (e.g. `microresolve-py/0.1.6`) surfaces in
  the connected-clients panel for "who's still on the old client?"
  triage.

### Changed

- **API key format now embeds the label**: `mr_<name>_<64 hex chars>`.
  The server extracts the name from the key string itself — no separate
  index lookup needed for attribution. Old opaque keys (`mr_<hex>`
  without the embedded name) are no longer accepted; v0.1 is pre-stable
  and operators must regenerate keys via the Studio UI / `/api/auth/keys`.
- Auth-key names must be slug-safe: `[a-z0-9][a-z0-9-]{0,30}` (no
  underscore — that's the field separator).

### Note

- Connected-clients tracking is **only active when API keys are
  configured**. In open mode (no keys), the panel is empty by design —
  there's no identity to attribute connections to.

---

## [0.1.5] — 2026-04-30

### Breaking

- **Connected-mode protocol unified.** The three v0.1.4 endpoints
  (`GET /api/sync`, `POST /api/ingest`, `POST /api/correct`) are
  removed and replaced by a single `POST /api/sync` that carries the
  library's buffered logs + corrections + per-namespace local versions
  in one request, and returns deltas in one response. v0.1.4 clients
  cannot talk to v0.1.5 servers; pin matching versions for now.
- `MicroResolve.correct(...)` no longer makes an immediate HTTP call.
  The correction is applied locally on the spot and shipped to the
  server on the next sync tick. The server reconciles within
  `tick_interval_secs`. Eliminates the per-correction stampede risk
  at scale and gives clean eventual-consistency semantics.
- v0.1 is pre-stable; expect more breaking protocol changes before 1.0.

---

## [0.1.4.1] — 2026-04-30

### Changed

- **Studio binary now ships with UI embedded.** The downloaded GitHub
  Release tarball is a single executable — no sibling `ui/` directory
  required, no `npm run dev` step. Implementation: `rust-embed` with
  compression bakes `ui/dist/` into the binary at compile time; the
  `server` feature now implies the new `bundled-ui` feature so building
  the studio is `cargo build --release --features server --bin
  microresolve-studio` (single flag). Build prerequisite from source:
  `cd ui && npm run build` once before cargo build, so `ui/dist/` exists.
- Crate description latency claim updated `~30μs` → `~50μs` to match
  the v0.1.4 README.

### Note

- Library users (PyPI / npm / crates.io) are unaffected — `bundled-ui`
  only activates when the `server` feature is enabled, which embedded
  bindings never enable.

---

## [0.1.4] — 2026-04-30

### Added

- **Connect-mode examples** — `examples/connected.rs` (Rust) and
  `python/examples/connected.py` (Python): minimal end-to-end demo of
  a library instance subscribing to a running Studio, resolving
  locally, pushing a correction, and watching the version bump on the
  next sync tick. This loop — Studio writes, library subscribes, live
  sync — is the headline feature of this release.
- **Prebuilt server binaries** via GitHub Releases: linux-gnu x86\_64 +
  aarch64, linux-musl x86\_64, darwin x86\_64 + aarch64,
  windows-msvc x86\_64. Install: download the tarball for your platform
  and put `microresolve-studio` on `$PATH`.

### Changed

- **Binary renamed** — `server` → `microresolve-studio` in Cargo.toml;
  the old `server` bin name is removed. Update any scripts or service
  files that reference the old name.
- **TCP bind failure** — server now exits cleanly with a descriptive
  error when the port is already in use; previously it panicked.
- **README** — hero rewritten to "learnable reflex layer for LLM apps";
  added "In the box" block (Studio / Library / online learning / native
  imports / multilingual / multi-namespace); Quick Start moved above
  the comparison table; v0.1 disclaimer condensed to one line; median
  resolve latency updated to 50µs.

---

## [0.1.3] — 2026-04-28

### Breaking

- **`Engine` renamed to `MicroResolve`** across all bindings (Rust, Python, Node.js).
  Update all imports and call sites:
  - Rust: `Engine` → `MicroResolve`, `EngineConfig` → `MicroResolveConfig`
  - Python: `from microresolve import Engine` → `from microresolve import MicroResolve`
  - Node.js: `const { Engine } = require('microresolve')` → `const { MicroResolve } = require('microresolve')`

---

## [0.1.0] — TBD

Initial public release. **MicroResolve** is a pre-LLM reflex layer for
intent classification, safety filtering, and tool selection — sub-millisecond,
CPU-only, with continuous learning from corrections.

### Library API

- **`MicroResolve` + `NamespaceHandle`** — multi-namespace decision engine. One
  instance per application can run several classifiers in parallel: security
  / mood / MCP tool selection / intent. The single public entry point.
  (Previously named `Engine`; renamed in v0.1.3.)
- **`MicroResolveConfig`** with cascade: `data_dir`, `default_threshold`,
  `languages`, optional LLM config, optional server config for connected
  mode. (Previously named `EngineConfig`; renamed in v0.1.3.)
- **`NamespaceConfig`** — per-namespace overrides for threshold,
  languages, LLM model.
- **Connected mode** — set `MicroResolveConfig.server` to a `ServerConfig` and
  the engine pulls subscribed namespaces from a server on startup,
  spawns a single background sync thread, buffers query logs for
  shipping, and pushes corrections inline. Replaces the legacy
  `AppRouter` surface.
- **Typed errors** — `Error::IntentNotFound`, `Io`, `Parse`,
  `Persistence`, `Connect`. No `Result<_, String>` in the public API.
- **`add_intent`** accepts `&[&str]` (English) or
  `HashMap<lang, Vec<phrase>>` (multilingual). Returns
  `Result<usize, Error>` (count of phrases indexed).
- **`update_intent` / `update_namespace`** patch metadata fields with
  `IntentEdit` / `NamespaceEdit` structs.
- **`correct(query, wrong, right)`** — one call moves a phrase between
  intents and reinforces; in connected mode, also pushes to the server.

### Bindings

- **Rust crate** `microresolve` on crates.io. Default features include
  connected mode (`reqwest`); embedded users can opt out via
  `default-features = false`.
- **Python** `microresolve` on PyPI — full `MicroResolve` + `Namespace` API
  via PyO3 / maturin. Mono and multilingual `add_intent`, `resolve`,
  `correct`, `intent`, `update_intent`, `add_phrase`, `version`,
  `flush`. Connected mode supported.
- **Node.js** `microresolve` on npm — `MicroResolve` + `Namespace` API via
  napi-rs. Connected mode supported.

### Server

- **HTTP API** (`microresolve-server`, `--features server`) wraps the
  same `MicroResolve` instance for multi-tenant deployments.
  - `/api/route_multi` — classify a query.
  - `/api/intents`, `/api/namespaces`, `/api/domains` — CRUD.
  - `/api/import/openapi/*`, `/api/import/mcp/*` — bulk-import tools.
  - `/api/sync`, `/api/ingest`, `/api/correct` — connected-mode endpoints
    for SDK clients.
  - `/api/auth/keys` — API key management for connected-mode auth.
- **Background auto-learn worker** — ingested query logs are
  LLM-judged in batch; corrections applied to the live namespace.
- **API key auth** — `X-Api-Key` middleware; keys stored at
  `~/.config/microresolve/keys.json` (separate from data dir, never
  git-tracked).

### Git-versioned training data

- The `--data` directory is automatically a git repo. Every namespace
  mutation auto-commits with a meaningful message.
- `GET /api/namespaces/{id}/history?limit=N` — list commits scoped to
  a namespace.
- `GET /api/namespaces/{id}/diff?from=&to=` — semantic diff between two
  commits: intents added/removed, phrases added/removed, metadata
  changes, weight-update count. Filters out raw L1/L2 weight noise.
- `POST /api/namespaces/{id}/rollback` — `git reset --hard` + reload
  affected resolvers from disk.
- `GET /api/settings/git` / `PUT /api/settings/git` — runtime configure
  a `git remote origin` so each commit pushes to a real GitHub/GitLab
  repo. Auth uses whatever git is configured with on the server.
- `POST /api/git/push` — manual push trigger.

### Studio UI

- React + Vite single-page app served by the binary or run via
  `vite dev` against an external server.
- **Namespaces, Intents, Domains, Layers, Languages, Models** management
  pages.
- **Studio (Resolve / Simulate / Review)** — live classification, batch
  simulation, query review queue, manual + auto-learn corrections.
- **History** — first-class sidebar entry. List-sidebar + main-pane
  layout. For each selected commit: sha, relative time, message, and
  author; amber warning when rolling back will discard newer commits;
  semantic diff panel showing affected intents, sample phrases under
  each added/removed intent, phrases-added/removed grouped by intent,
  metadata changes side-by-side (before/after), and training-weight
  summary; one-click rollback with confirmation.
- **Settings → Data sync (Git)** — set/clear the git remote URL,
  manual push button, status indicator.
- **Settings → Auth Keys** — generate, list, revoke API keys.
- **Import** — OpenAPI / MCP / LangChain / OpenAI function-tool import
  flows with LLM-assisted seed-phrase generation.

### Internal

- `Resolver` and the `scoring` / `ngram` / `phrase` / `tokenizer` /
  `connect` modules are `#[doc(hidden)]`. Library users see only
  `MicroResolve`, `NamespaceHandle`, and the public types in rustdoc.
- Server bin migrated off direct `Resolver` use to `MicroResolve` API
  internally — single source of truth for namespace state.
- Cargo.toml `exclude` list keeps the published crate at ~174 KB
  (only `src/`, `examples/*.rs`, `tests/*.rs`, `languages/*.json`,
  `LICENSE-*`, `README.md`, `CHANGELOG.md`).
- License: dual MIT / Apache 2.0 across all artifacts (root crate,
  Python wheel, Node tarball, UI bundle, docs site).

### Benchmarks

Benchmark numbers will be added to this section after the v0.1.0
benchmark sweep runs. End-to-end smoke against Llama 3.3 / Groq
verifies multi-namespace MCP import, cross-namespace isolation,
manual corrections, and the auto-learn loop in ~75 seconds.

### Known limitations

- Server bin still uses `pub fn with_resolver(_mut)` closure escape
  hatches (marked `#[doc(hidden)]`). True `pub(crate) struct Resolver`
  is a v0.2 cleanup that doesn't change behaviour.
- Node binding is missing `resolve_with`, `intent`, `update_intent`,
  `add_phrase` methods that Python has (v0.2).
- History UI shows commit list + diff but no commit-graph visualisation
  or filter chips.
- No CLA — contributions are accepted under the dual MIT/Apache license.

---

[0.1.0]: https://github.com/gladius/microresolve/releases/tag/v0.1.0
