# Changelog

All notable changes to this crate are documented here.

## [Unreleased]

## [2.2.0] — 2026-06-18

### Added

- Per-tenant LLM budgeting (ADR-051 §3 / Phase B B4). `Governor` gains an
  additive default method `acquire_llm_scoped(scope: BudgetScope, est_tokens)`
  (delegates to `acquire_llm` for back-compat; no existing signature changes).
  `TokenBucketGovernor` overrides it: `BudgetScope::Provider` keeps the existing
  fail-closed provider buckets byte-for-behavior (the `SupervisedAgent` path is
  unchanged), while `BudgetScope::TenantProvider` uses new per-`(tenant,
  provider)` buckets with an on-demand bounded default (`tenant_llm_default_rps`,
  builder `tenant_llm_default_rps` / `tenant_llm_limit` overrides) and fail-fast
  saturation. `GovernedLlmClient` is now public (`#[non_exhaustive]`) and takes
  an `Option<TenantId>` so it scopes per tenant when set. Request-rate (RPS)
  only; token/TPM budgeting is a follow-up. Additive — minor.
- `DefaultRedactor` now implements `klieo_core::redact::AuditRedactor`
  (A3.2) by delegating to its existing `Redactor::redact`, so the same
  shape-preserving PII masking can be injected into `AgentContext` as
  the audit redactor for PII-flagged tools. Existing `Redactor`
  behaviour is unchanged. Additive — minor.
- `GatedTool` (the `Tool` wrapper) now forwards `Tool::redacts_audit()`
  to its inner tool, so wrapping a PII-flagged tool with a gate no
  longer drops the redaction flag. Additive — minor.

### Fixed

- **Security: `GatedToolInvoker` failed to forward redaction +
  idempotency flags (A3.2).** The decorator forwarded only `invoke` and
  `catalogue`, so `tool_redacts_audit(name)` and `is_tool_idempotent(name)`
  fell back to the `ToolInvoker` defaults (`false`) for every wrapped
  tool. A PII-flagged tool placed behind a gate would therefore fail
  OPEN — dispatch recorded raw PII to the audit trail instead of a
  redacted projection. Both methods now forward to the wrapped invoker.
  No public-API change; minor.

## [0.6.0] — 2026-05-17

### Breaking

- **Ciphertext wire format change (B4 — second iteration).** Dropped
  the leading `version_u8` byte that v0.5 added. v0.6 format:
  `nonce || aad_len_u16_le || aad || ct||tag`. The version still lives
  in the AAD trailing byte (auth-tag-covered). The leading byte was
  redundant.

  **v0.5 ciphertext does NOT decrypt under v0.6.** Migration:
  - Pre-prod: drop encrypted KV state.
  - Production: deploy a one-shot reader that detects v0.5 vs v0.6
    framing by trial-decode (v0.5 ciphertext is exactly 1 byte longer
    than v0.6 for the same plaintext), decrypts under v0.5, re-encrypts
    under v0.6. The framework does not ship this tool.
  - **Stable from v0.6 onward.** v0.7+ commits to backward-compatible
    framing additions (e.g. AEAD algorithm versioning happens inside
    the AAD payload, not by changing the prefix).

- **`HandoffError::Unverifiable { reason }` new variant.** Returned by
  the new `verify_opaque` method when `SignatureInvalid` or
  `UntrustedSource` would otherwise be raised. `HandoffError` is
  `#[non_exhaustive]` so this is SemVer-minor for downstream pattern
  matches with a `_` arm; flagged as Breaking here because callers
  consuming the typed error must update their match arms to cover the
  new variant if they want exhaustive treatment.

### Added

- B6: `Handoff::verify_opaque(envelope) -> Result<HandoffProof, HandoffError>`
  defaulted method. Collapses `SignatureInvalid` + `UntrustedSource`
  into `Unverifiable { reason: "" }`. Default impl in the trait;
  `KvHandoff` inherits. Public-boundary services should prefer this.
- B7: `FourEyesGate::with_sync_bridge_timeout(d: Duration)` setter
  behind `#[cfg(any(test, feature = "test-utils"))]`. Used by tests
  to verify the sync-bridge timeout path; not a production API.

### Changed

- Crypter `decrypt` no longer validates the leading version byte
  (it's gone). All AAD validation still happens via the trailing
  version byte under AEAD authentication.
- `run_sync` now accepts `timeout: Duration` as a parameter instead of
  reading the module-level `SYNC_BRIDGE_TIMEOUT` constant. Production
  default is unchanged at 30s; tests inject a short timeout via
  `with_sync_bridge_timeout`.

## [0.5.0] — 2026-05-17

### Breaking

- **Ciphertext wire format change (B1).** `TenantKeyedCrypter`
  ciphertext framing changed from `nonce || ct||tag` (v0.4) to
  `version_u8 || nonce || aad_len_u16_le || aad || ct||tag` (v0.5).
  AAD = `tenant_bytes || version_byte`.

  **v0.4 ciphertext does NOT decrypt under v0.5** — the new framing
  expects a version byte + AAD-length prefix that the v0.4 format
  does not carry. Adopters with at-rest encrypted episodic data must
  either (a) decrypt + re-encrypt under v0.5 with a one-shot migration
  tool, or (b) keep a v0.4-compatible reader for legacy data and
  encrypt new writes under v0.5. The framework does not ship a
  migration tool; the format detection (`v0.5` always starts with
  `version_u8 == 0` in this release; future versions will increment)
  is the basis for a dual-path reader if needed.

  Mitigation for pre-prod deployments: drop encrypted KV and let new
  data write under v0.5. Production deployments: do NOT upgrade
  without a migration plan.

### Added

- B7: Status-index sharding in `KvWorkLog` (16 shards by hash(work_id))
  distributes per-status hotkey contention across the bucket.
- B1: `Crypter` AAD binding — see Breaking Changes for the wire format
  contract. New `CrypterError::AadMismatch` variant signals
  cross-tenant decrypt rejection or key-rotation mismatch before the
  AEAD tag check fires.
- `KeyProvider::lookup_versioned(&Option<TenantId>) -> Option<KeyMaterial>`
  defaulted method (returns version=0 for existing impls).
- C: `HotReloadableEd25519Registry<Id>` behind the new `hot-reload`
  feature (opt-in; pulls `notify` + `arc-swap`). Generic over the
  `Ed25519KeyRegistry<Id>` backbone so the wrapper serves both
  `ApproverRegistry` and `SourceIdentityRegistry`. Watches a YAML file,
  debounces 500ms, atomically swaps via arc-swap on parse success;
  failures keep the previous snapshot + emit tracing::warn.

### Changed

- B2: `FourEyesGate::evaluate` now fails CLOSED on initial-KV write
  failure with `GateDecision::Deny { code: "four_eyes.kv_unavailable" }`
  instead of advertising a ticket that doesn't exist in KV.
- B3: `FourEyesGate` `submit_approval`/`deny_approval` sync→async bridge
  wraps `block_on` in `tokio::time::timeout(30s)`. Hung KV no longer
  deadlocks the caller; surfaces as `ApprovalError::VerificationFailed`.
- B4: `KvHandoff::verify` order swapped from `expiry → identity → sig`
  to `expiry → sig → identity`. Closes the registry-membership oracle —
  attacker probing arbitrary identities now gets only
  `SignatureInvalid`.
- B5: `WorkLog::get` now emits `tracing::warn!` on transport failure
  before returning `None` (trait signature unchanged; behaviour
  semantically the same; failures observable).
- B6: `KvWorkLog::list_by_status` logs on `try_join_all` failure
  instead of silent `unwrap_or_default`.

## [0.4.0] — 2026-05-16

### Added

- Spec § 2.3 RequeueAndEscalate default approval-timeout policy:
  `ApprovalTimeoutPolicy { Deny | RequeueAndEscalate { max_requeues } }`
  with default `RequeueAndEscalate { max_requeues: 3 }`. GatedToolInvoker
  loops up to max requeues, emits EscalationStateChanged per requeue,
  emits EscalationResolved on exhaustion.
- Spec § 1.7 source-identity trust enforced at handoff verify:
  `SourceIdentityRegistry` trait + `StaticSourceIdentityRegistry`
  (YAML+base64). `KvHandoff::new(kv, trust_registry)` — registry now
  required. `verify()` rejects with `HandoffError::UntrustedSource` on
  unknown identity or verifying-key mismatch before signature math.
- Spec § 1.10 multi-process WorkLog: items + parents/children
  adjacency + status-index buckets persisted to KV. CAS-retry on
  conflict. In-process DashMaps removed (KV is source of truth).
- Spec § 1.10 multi-process FourEyesGate: pending approval state
  persisted to KV. wait_for_approval polls KV (with local Notify
  fast-path) so submit_approval on one process wakes wait on another.
- M5.1b residual: auto-wire `with_audit` for Escalation/Worklog/Handoff
  via task-local RunContext. New helpers `audited_escalation`,
  `audited_worklog`, `audited_handoff` produce audit-wired primitives
  inside SupervisedAgent::run scope.
- OQ-3 SignerProvider trait + SoftwareSigner default impl + SignedPayload
  type. KvHandoff::package now takes `&dyn SignerProvider` for HSM/KMS
  adapter pattern.
- OQ-4 per-tenant Cedar policy bundles. PolicyGate holds
  `HashMap<Option<TenantId>, PolicySet>`; `evaluate()` reads
  `current_tenant()` and falls back to default bundle, then
  fail-CLOSED with `policy.no_bundle_for_tenant`.
- OQ-5 Crypter + KeyProvider for at-rest encryption with crypto-shred
  semantics. `TenantKeyedCrypter` (AES-256-GCM) + `StaticKeyProvider`.
  Integration with EpisodicMemory adapters is documented pattern per
  spec § 1.9.

### Changed

- `KvHandoff::new` now takes `Arc<dyn SourceIdentityRegistry>` as a
  required second argument. v0.3 callers must construct a trust
  registry.
- `FourEyesGate::new` now takes `Arc<dyn KvStore>` as a required first
  argument for multi-process pending state.
- `Handoff::package` takes `&dyn SignerProvider` instead of `&SigningKey`.

### Removed

- `KvWorkLog` in-process DashMap caches removed; KvStore is source of
  truth.

## [0.3.0] — 2026-05-16

### Added

- M5 foundation completion:
  - klieo-runlog wiring for `Episode::Ops` projection; new
    `StepKind::OpsEvent` carries the typed payload.
  - `emit_ops_event(memory, run_id, event)` helper for live emission.
  - `klieo-pii-patterns` leaf crate — single source of truth for PII
    regex sources; consumed by both `DefaultRedactor` and the
    evidence verifier's bit-4 check.
  - TAXID_DE pattern tightened to require a contextual prefix
    (steuer / tax id) — avoids over-matching arbitrary 11-digit values.
  - Cedar context lifting completeness: nested objects → Cedar records,
    arrays → Cedar sets, floats → Cedar decimals, u64 overflow → Deny,
    null → Deny via `cedar.context.unrepresentable`.
  - Real `governed_client` via `reqwest-middleware`; out-of-allowlist
    hosts rejected pre-flight, per-host governor permits acquired per
    request. `#[deprecated]` annotation dropped.
- M5.1b auto-emission:
  - `SupervisedAgent::run` now auto-emits Supervisor / Governor / Gate
    events into `EpisodicMemory` without user code calling
    `emit_ops_event` manually.
  - `GovernedLlmClient` wraps `ctx.llm`: acquires permits per
    complete/stream/embed; emits `OpsEvent::GovernorDenial` on
    saturation/timeout.
  - `GatedToolInvoker` wraps `ctx.tools`: runs gate stack pre-effect;
    emits `OpsEvent::GateDecision` per decision.
- M6 Escalation primitive:
  - `Escalation` trait + `JobQueueEscalation` default impl.
  - Three reference channel adapters: `LogChannel`, `WebhookChannel`,
    `JobQueueChannel`.
  - `EscalationRaised / EscalationStateChanged / EscalationResolved`
    OpsEvent variants.
  - Builder dependency validation: gates with
    `may_require_approval == true` require an Escalation.
- M7 Worklog primitive:
  - `WorkLog` trait + `KvWorkLog` default impl.
  - DAG cycle detection, auto-Ready propagation on dependency Done,
    configurable size cap.
  - `klieo-ops-redispatcher` binary for stale-item recovery.
  - `WorkPlanned / WorkDependencyAdded / WorkDispatched /
    WorkTransition` OpsEvent variants.
- M8 FourEyesGate primitive:
  - `FourEyesGate` with classifier-driven `RequireApproval` emission.
  - `ApproverRegistry` trait + `StaticApproverRegistry` (YAML + base64
    load).
  - Quorum verification = math-valid sig + registry membership +
    unique-id quorum dedup.
  - `Gate::wait_for_approval` defaulted method; `GatedToolInvoker`
    awaits resolution before allowing the tool call to proceed.
  - `ApprovalError` + `ApprovalOutcome` public types.
- M9 Handoff primitive:
  - `Handoff` trait + `KvHandoff` default impl.
  - `HandoffEnvelope` with Ed25519 signature over canonical-JSON
    payload (from_run + merkle_root + state_hash + source_identity +
    tenant + issued_at + expires_at).
  - `HandoffProof` is `#[non_exhaustive]` — receivers can only adopt
    via a successful `verify()` call.
  - Default 1h TTL; configurable per `package` call.
  - `HandoffPackaged / Delivered / Verified / VerificationFailed`
    OpsEvent variants.

### Changed

- `escalation`, `worklog`, `handoff` added to the default feature set.
- `ed25519-dalek` is now an unconditional dependency (was gated on
  `handoff` feature in v0.2).
- Phase B integrity checks bits 2 (approver sig) and 3 (approver
  registry) are now closed in `klieo-ops-evidence-verify` when an
  evidence bundle contains approver resolutions.

### Fixed

- Cross-crate PII regex duplication between `klieo-ops::redactor` and
  `klieo-ops-evidence-verify` resolved via the shared
  `klieo-pii-patterns` crate.

## [Unreleased]
