- Add `locales` field to SearchUiIndexConfig (HashMap<lang, translations>)
- Enable operators to configure custom translations via config endpoint
- JavaScript already has i18n support (lang query param, fallback to en)
- Add documentation for operators on how to configure locales
Acceptance: GET /ui/search/{index}?lang=fr returns French UI strings when
fr locale configured; falls back to en.
Implements admin API endpoints and CLI commands for managing tenant
mappings (api_key mode) as specified in plan §13.15:
Admin API endpoints:
- POST /_miroir/tenants - Add a tenant mapping (api_key → tenant_id → group_id)
- GET /_miroir/tenants - List all tenant mappings
- DELETE /_miroir/tenants - Delete a tenant mapping by api_key
CLI commands (miroir-ctl tenant):
- miroir-ctl tenant add --api-key KEY --tenant ID --group N
- miroir-ctl tenant list
- miroir-ctl tenant remove --api-key KEY
TaskStore changes:
- Added list_tenant_mappings() method to TaskStore trait
- Implemented in SQLite and Redis backends
- Updated all MockTaskStore implementations in test files
Security: API keys are hashed using SHA-256 before storage (never stored
plaintext). Mappings are persisted to task_store for HA deployments.
Closes: bf-38mn2
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
When aliases.require_target_exists config is set to true, alias creation
and updates now validate that the target index exists on all Meilisearch
nodes before proceeding.
Replaced two TODOs in routes/aliases.rs with actual implementation:
- create_alias: validates single target and all multi-targets
- update_alias: validates new target on alias flip
The check uses Meilisearch's GET /indexes/{uid} endpoint which returns:
- 200 if index exists
- 404 if index not found
- Other HTTP errors for connectivity/auth issues
Closes: bf-gfiw8
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previously GET /_miroir/indexes/{uid}/reshard/status returned hardcoded 0
for documents_backfilled and total_documents. This commit:
1. Adds documents_backfilled and total_documents fields to ReshardOperationState
2. Adds update_progress() method to ReshardingRegistry
3. Adds progress_callback to ReshardOrchestratorConfig
4. Updates the HTTP endpoint to return actual progress values
5. Updates all test cases to include the new fields
The progress_callback is invoked after backfill completes to update the
registry with the final document counts. The status endpoint now returns
real progress data instead of hardcoded zeros.
Closes: bf-22jkc
Complete all TODO integrations in explainer.rs for query explain output:
- Alias lookup in task store with version info
- Tenant affinity resolution with hash fallback
- EWMA latency from replica selection
- Comprehensive test coverage for all integrations
- Updated miroir-ctl explain command with full output formatting
Closes: bf-5pico
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implement POST /_miroir/canaries/capture endpoint to record production
queries + responses as golden pairs for canary testing (plan §13.18).
Changes:
- Add CaptureSession to QueryCapture with target_index, max_count, name_prefix
- Add start_capture(), stop_capture(), is_capturing(), get_session() methods
- Update start_capture endpoint to accept {"index", "count", "name_prefix"}
- Add query_capture field to AppState and wire through search handler
- Capture queries in search path when capture session is active
- Update capture flow tests to start capture sessions before capturing
Closes: bf-14xmh
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Integrated QueryPlanner into the search request path to enable shard-aware
query optimization. PK-constrained searches now fan out to only the relevant
shards instead of the full covering set.
Changes:
- miroir-proxy/src/routes/search.rs: Call QueryPlanner before scatter planning
and use plan_search_scatter_with_narrowing with narrowed target_shards
- miroir-core/src/explainer.rs: Add QueryPlanner integration to Explain API
for visibility into query planning decisions
- miroir-proxy/src/routes/explain.rs: Update to pass QueryPlanner to Explainer
Acceptance criteria met:
1. ✅ QueryPlanner called before scatter-gather for every search request
2. ✅ Filter expressions parsed to identify PK-constrained searches
3. ✅ PK-lookups route to single shard (via narrowed target_shards)
4. ✅ Explain API shows query planning decisions (narrowed, narrowing_reason)
5. ✅ Tests validate planner narrows fan-out correctly
Performance impact: PK-lookups now fan out to 1 shard instead of all S shards
(expected ~10x faster for PK-lookups as per plan §13.4).
Note: Primary key registration with QueryPlanner during index creation is
tracked separately (future bead). The QueryPlanner returns "primary key not
configured for index" for indexes where PK hasn't been registered yet,
falling back to full covering set.
Closes: bf-mknij
Implements POST/GET/DELETE /_miroir/indexes/{uid}/ttl-policy and
GET /_miroir/ttl-policies for per-index TTL sweep policy configuration.
Adds:
- Task store table 16 (ttl_policy) with SQLite and Redis backends
- Migration 006_ttl_policy.sql
- Endpoint handlers for CRUD operations on TTL policies
Accepts: {sweep_interval_s, max_deletes_per_sweep, enabled} to override
global ttl.* settings per index.
Closes: bf-2pgb4
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add multipart/form-data file upload support for POST /_miroir/dumps/import
- Implement fallback broadcast mode for dump_import config
- Update CLI to use multipart upload instead of JSON base64
- Add axum multipart feature to miroir-proxy
- Add reqwest multipart feature to miroir-ctl
- Update test to reflect broadcast mode acceptance
Acceptance criteria met:
- Streaming import routes documents per-shard (not 100% to each node)
- Large imports complete with batched per-target writes
- Metrics track bytes read, documents routed, rate
- Fallback broadcast mode works when streaming is disabled
Closes: bf-4u2n4
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previously the reshard orchestrator config had a None metrics_callback,
meaning no Prometheus metrics were emitted during reshard operations.
This commit implements the metrics callback to update:
- miroir_reshard_in_progress: gauge set to 1 during active resharding, 0 when idle/complete/failed
- miroir_reshard_phase: gauge tracking current phase (0=idle, 1=shadow, 2=dual_write, 3=backfill, 4=verify, 5=swapped, 6=cleanup, 7=complete, 8=failed)
- miroir_reshard_documents_backfilled_total: counter incremented with document counts during backfill and later phases
The callback uses the public Metrics API methods (set_reshard_in_progress,
set_reshard_phase, inc_reshard_documents_backfilled) and correctly maps
ReshardPhase enum variants to their corresponding phase numbers.
Closes: bf-4wza
Fix the signature of `renew_leader_lease` to accept `now_ms` as a parameter
instead of calling `now_ms()` internally. This ensures time consistency
across the lease renewal check and improves testability.
Changes:
- Add `now_ms: i64` parameter to `TaskStore::renew_leader_lease` trait
- Update all call sites to pass the current time explicitly
- Fix task_pruner to use a short TTL (1s) when releasing the lock
- Update drift_reconciler to pass the current time when renewing
This change prevents potential race conditions where the internal `now_ms()`
call could return a different time than the caller's context, which could
lead to incorrect lease expiration checks.
Gates passed: cargo check, clippy, fmt, nextest (non-Docker tests)
Plan §13.17 ILM (Index Lifecycle Management) worker integration.
- Add ilm_manager and ilm_worker fields to admin_endpoints::AppState
- Create IlmManager when config.ilm.enabled with task store and node addresses
- Spawn ILM worker in main.rs as Mode B background task
- Worker evaluates rollover policies and performs index rollovers when triggers fire
- ILM worker requires leader_election service and task store to operate
Acceptance: ILM worker spawned in main.rs like other Mode B workers,
runs leader-coordinated evaluation loop per plan §14.5.
Closes: bf-509r
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Added rate_limit() method to ErrorResponse for proper HTTP 429 responses
- Added check_detailed() to LocalSearchUiRateLimiter returning (allowed, remaining, reset_after)
- Implemented IP-based rate limiting in mint_session using Redis or local backend
- Extracts client IP from X-Forwarded-For or X-Real-IP headers
- Parses rate limit config (e.g., "60/minute" -> limit=60, window=60s)
- Returns accurate rate limit info (remaining, reset_in) in session response
The rate limit info is now tracked in Redis (miroir:ratelimit:searchui:<ip>)
or in local memory, with proper TTL handling.
Closes: bf-607z
The multi-search route was hardcoding over_fetch_factor to 1 instead of
using the configured vector_search.over_fetch_factor value. This meant
vector searches in multi-query batches didn't benefit from over-fetching,
leading to incorrect global ranking on sparse semantic matches.
Changes:
- Added HeaderMap parameter to multi_search handler
- Extract X-Miroir-Over-Fetch header for per-request override (plan §13.12)
- Pass over_fetch_factor into the executor closure
- Use over_fetch_factor when building SearchRequest
Closes: bf-5204
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Remove trailing blank lines in lib.rs
- Improve line breaking in documents.rs test
- Other minor formatting consistency fixes
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds clap-based CLI argument parsing so `miroir-proxy --version`
and `miroir-proxy --help` print version/usage and exit instead
of starting the server and hanging.
Also fixes numerous pre-existing clippy warnings in test files:
- digit grouping inconsistencies
- unused functions/variables
- useless_vec (vec! -> array)
- assert!(true) placeholders
- too_many_arguments
Resolves: bf-31ff
- Remove unused type parameter S from explain_search function
- Add peer-discovery feature to miroir-proxy Cargo.toml
- Fix unused variables by prefixing with underscore
- Add #[allow(dead_code)] to modules with unused public API functions
Resolves clippy -D warnings for lib and binary targets.
- Run cargo clippy --fix to apply uninlined format args suggestions
- Fix deprecated IndexMap::remove calls in session_pinning.rs (use shift_remove)
- Various test and source files updated by clippy auto-fix
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Remove trailing whitespace from multiple files
- Minor formatting fixes across crates
- Net reduction of 69 lines of whitespace
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Minor formatting adjustments for consistency:
- Fix indentation in template validation logic
- Fix indentation in timing gate check
These are cosmetic changes that improve code readability
without affecting functionality.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Apply cargo clippy --fix to remove unused imports, prefix unused
variables with underscore, and fix various clippy warnings across
miroir-core, miroir-proxy, and miroir-ctl.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Compute shard_count per node using rendezvous hash assignment
- Compute last_seen_ms from node.last_seen (milliseconds since last health check)
- Populate error field from node.last_error
This completes the plan §10 topology endpoint JSON shape requirements.
Closes: bf-3jy5
Adds comprehensive acceptance tests for plan §10 OpenTelemetry tracing:
- Verify tracing.enabled=false returns None (zero overhead)
- Verify default config has tracing disabled
- Verify sample_rate config parsing (default 10%)
- Verify resource attributes (service.name, endpoint, POD_NAME)
- Verify feature flag controls compilation
- Verify shutdown_otel is safe to call multiple times
- Verify span hierarchy exists in scatter path code
- Verify TracingConfig serde round-trip (JSON/TOML)
Also makes the otel module public via lib.rs for test access,
and adds toml as a dev dependency for config parsing tests.
All 15 tests pass. Closes: miroir-afh.6
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implement analytics beacon endpoint with idempotency and CDC integration:
- Add `check_and_mark_beacon_event` to TaskStore trait for idempotency
- Implement for both Redis (HSET with 24h TTL) and SQLite (table with cleanup)
- Add JWT session extraction for session_id in beacon events
- Add server-side event_id generation fallback for old browsers (SHA256 hash)
- Integrate with CDC manager to publish AnalyticsEvents (click_through, latency)
- Respect cdc.emit_internal_writes for latency events
- Add Display impl for JwtValidationError for proper error logging
- Add jwt_decode_with_fallback helper for JWT rotation support
- Add unit tests for beacon idempotency (SQLite and Redis)
Closes: miroir-uhj.21.6
Implement admin UI login/logout endpoints with CSRF protection, rate limiting,
and session management per plan §13.19.
Login endpoint (POST /_miroir/admin/login):
- Generate session ID and CSRF token
- Store session in task store with CSRF token
- Return sealed session cookie (HttpOnly, Secure, SameSite=Strict)
- Return CSRF token in response body
- Rate limiting: 10/minute per IP with exponential backoff after 5 failures
- Origin validation against admin_ui.allowed_origins
Logout endpoint (POST /_miroir/admin/logout):
- Revoke session in task store
- Clear session cookie (Max-Age=0)
- Redis Pub/Sub propagation for multi-pod deployments
Session endpoint (GET /_miroir/admin/session):
- Validate session and check revocation status
- Return fresh CSRF token on each call
- Check expiration time
Implementation notes:
- Uses task_store trait (supports both Redis and SQLite backends)
- CSRF tokens generated with crypto-random 32-byte values
- Admin key hashed with SHA-256 before storage (never store plaintext)
- Rate limiting supports redis and local backends
- Session TTL configurable via admin_ui.session_ttl_s (default 3600s)
Closes: miroir-uhj.19.5
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implements GET and PATCH /_miroir/settings endpoints for the Admin UI
Settings section (plan §13.19). The endpoints allow operators to view
and update Miroir's configuration with proper validation.
- GET /_miroir/settings: Returns the full Miroir configuration
- PATCH /_miroir/settings: Updates configuration with restart guards
Restart-required settings (rejected at runtime):
- shards, replication_factor, replica_groups (topology changes)
- nodes (node list changes)
- task_store.backend (backend type changes)
- anti_entropy.enabled (feature flag changes)
- master_key, node_master_key (secrets)
Runtime-updatable settings:
- rebalancer.max_concurrent_migrations
- rebalancer.migration_timeout_s
- query_planner.mode
- session_pinning.enabled
- anti_entropy.schedule
The PATCH endpoint performs deep merge of JSON payloads and validates
the resulting configuration before applying.
Closes: miroir-uhj.19.4
Updated `serve_admin_ui` to accept requests authenticated via admin
session cookie (set by `/admin/login`), in addition to the existing
X-Admin-Key and Authorization: Bearer header methods.
The auth middleware already unseals the session cookie and sets the
`AdminSessionId` extension - the UI handler now checks for this extension
to allow cookie-authenticated requests through.
Added comprehensive unit tests for:
- X-Admin-Key authentication
- Bearer token authentication
- Session cookie authentication (via extension)
- File serving with proper cache headers
- 404 for missing files
The embedded admin UI assets are ~35 KB gzipped (well under the 100 KB
requirement). Session sealing, CSRF, and cross-pod session invalidation
were already implemented in prior work.
Closes: miroir-uhj.19
Update add_node and drain_node endpoints to return 202 Accepted with
miroir_task_id in the response, matching the P4.6 spec.
Changes:
- add_node now returns 202 with miroir_task_id (rebalance:default)
- drain_node now returns 202 with miroir_task_id (rebalance:default)
- Both endpoints include task ID in logging for observability
- Added response shape documentation to both endpoints
Closes: miroir-mkk.6
- Add VectorMode re-export to miroir-core lib.rs
- Add missing vector_config field to SearchRequest and MergeInput in tests
- Fix admin_ui.rs test assertion (Result doesn't impl Eq)
- Fix auth.rs CSRF test (remove Next::new usage that doesn't compile in axum 0.7)
These were compilation errors introduced after adding vector_config field to
search structs. All 173 miroir-proxy library tests now pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Changed assert_eq! to separate is_err() and unwrap_err() calls
since axum::http::Response doesn't implement PartialEq.
Closes: miroir-m9q.6
The HPA implementation is complete with:
- miroir-hpa.yaml template with all required metrics (cpu, memory,
miroir_requests_in_flight, miroir_background_queue_depth)
- values.schema.json validation (hpa.enabled requires replicas >= 2
AND taskStore.backend=redis)
- Test files for schema validation (bad-hpa-single-replica.yaml,
bad-hpa-no-redis.yaml)
- values.yaml with per-workload-tier defaults (plan §14.7)
- prometheus-adapter ConfigMap for custom metrics
- NOTES.txt documenting prometheus-adapter prerequisite
Acceptance criteria require helm lint and kind cluster testing,
which are not available in this environment. The implementation
matches plan §14.4 specification exactly.
This completes the Mode A integration for horizontal scaling (plan §14.5):
- Wire drift_reconciler with mode_a_coordinator for settings drift check partitioning
- Wire anti_entropy_worker with mode_a_coordinator for shard-partitioned anti-entropy
- Wire canary_runner with mode_a_coordinator for rendezvous-owned canary execution
Changes:
- admin_endpoints.rs: Create mode_a_coordinator before workers, wire up using Arc::try_unwrap
- main.rs: Wire canary_runner with mode_a_coordinator when available
Acceptance criteria met:
- Unit test: owns() returns true for exactly one peer per item (existing test passes)
- 3 pods anti-entropy: each shard processed exactly once (existing test passes)
- Pod reassignment: shards reassigned within refresh window (existing test passes)
The Mode A coordinator was already fully implemented with rendezvous hashing.
This commit completes the wiring so workers actually use it.
Closes: miroir-m9q.3
Implements the required metrics for tracking dump import operations:
- miroir_dump_import_bytes_read_total: Counter for total bytes read
- miroir_dump_import_documents_routed_total: Counter for documents routed
- miroir_dump_import_rate_docs_per_sec: Gauge for current import rate
- miroir_dump_import_phase: GaugeVec tracking phase by index/import_id
Metrics are recorded:
- At import start: bytes_read and phase set to Reading
- At status check: documents_routed, import_rate, and current phase
Acceptance criteria addressed:
- Import rate metric tracks actual throughput visible in Grafana
Closes: miroir-uhj.9
Implements plan §13.21 auth model layer 1 - when search UI is first
enabled for an index, the orchestrator now creates a scoped search-only
key on every Meilisearch node via POST /keys with actions: [search],
indexes scoped. The key is stored in Redis hash with metadata
(primary_uid, rotated_at, generation) for retrieval at request time.
Changes:
- Add imports for MeilisearchClient and mint_scoped_key
- Implement get_or_create_scoped_key to create keys when needed
- Store new keys in Redis via set_search_ui_scoped_key
- Return the scoped key for use in JWT session minting
The scoped key has a hard expiration of scoped_key_max_age_days (60d
default) and will be auto-rotated by the background rotation loop at
scoped_key_rotate_before_expiry_days (30d default) - see P10.5 for
the rotation coordination implementation.
Closes: miroir-uhj.21.1
- Fix metric name collision between multi-search and tenant affinity session
pin override metrics. Rename multi-search metric to
`miroir_multisearch_tenant_session_pin_override_total` to avoid conflict.
- Fix `serve_search_ui` function to use correct `FromRef` pattern for
accessing config from generic state type.
- Add `admin_ui` module declaration to main.rs for binary compilation.
- Add missing `tenant_affinity_manager` field to FromRef implementation.
These changes fix compilation errors that prevented the codebase from building.
The P7.2 bead implementation (metrics gated behind feature flags) was already
complete in commit 7c13091.
Closes: miroir-afh.2
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Updates the CI workflow to:
1. Add HTML coverage report output (plan §8 coverage policy)
- Previously only generated Lcov + Xml formats
- Now also outputs Html for browser-based viewing
2. Publish coverage reports as Argo artifacts
- coverage-html/ directory for interactive browsing
- cobertura.xml for CI tool integration
- lcov.info for diff tools
3. Add PR comment showing coverage delta
- Posts coverage percentage on PRs when revision != main
- Shows current coverage vs 90% target vs base (main)
- Includes link to full coverage artifact
4. Generate coverage summary file for PR comment consumption
The coverage gate (--fail-under 90) was already in place; this adds
the visibility (artifacts + PR comments) required by plan §8.
Closes: miroir-89x.1
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Clean up middleware formatting for tenant affinity metrics
- Fix Node import in rebalancer worker tests
- Update anti_entropy worker type annotations
- Minor test improvements in chaos acceptance tests
These changes improve code readability and fix minor type issues.
Implements plan §13.16 traffic shadow functionality for validating
changes against real production traffic without risk.
**Core changes:**
- Add ShadowConfig conversion from config::advanced::ShadowConfig
- Initialize ShadowManager in AppState when shadow config is enabled
- Integrate shadow into search, multi_search, and explain flows
- Fix diff computation to accept primary hits for proper Kendall tau
**Shadow behavior:**
- Async shadows a configurable fraction of requests to staging cluster
- Primary response returned synchronously; shadow runs in background
- Diff worker compares hit sets, ranking order (Kendall τ), latency Δ
- Results stored in in-memory ring buffer (queryable via admin API)
- Shadow failures never impact primary latency or error rate
**Config:**
```yaml
shadow:
enabled: true
targets:
- name: staging
url: http://miroir-staging.search.svc:7700
api_key_env: SHADOW_API_KEY
sample_rate: 0.05
operations: [search, multi_search, explain]
diff_buffer_size: 10000
max_shadow_latency_ms: 5000
```
**Acceptance criteria met:**
- 5% sampling rate verified in tests
- Shadow cluster down → 0 impact on primary
- Ring buffer bounded; oldest evicted when full
- Writes never shadowed (operations filter enforced)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Closes: miroir-uhj.16
- Fixed RustEmbed folder path from "../../static/search/" to "static/search/"
- Changed error type from Result<T> to Result<T, ErrorResponse> for axum compatibility
- Replaced MiroirError::InvalidRequest with ErrorResponse::invalid_request
- Replaced MiroirError::Task with ErrorResponse::internal_error
- Fixed config parameter to pass &config.search_ui instead of &config
The RustEmbed derive was not working because the folder path was relative
to the file location instead of the crate root. Additionally, the error
type needed to be ErrorResponse (which implements IntoResponse) instead
of MiroirError for axum handler compatibility.
Closes: compilation errors in search_ui.rs
Add missing Prometheus metrics to the /multi-search endpoint:
- miroir_multisearch_queries_per_batch: histogram tracking query count per batch
- miroir_multisearch_batches_total: counter for total batches processed
- miroir_multisearch_partial_failures_total: counter for batches with >=1 failed query
The core MultiSearchExecutor and HTTP endpoint were already implemented.
This commit completes the observability requirements from plan §13.11.
All acceptance criteria covered by existing tests:
- 5-query batch: test_five_query_batch_all_complete
- Parallel execution: test_slow_query_doesnt_block_fast_queries
- 100-query batch: test_large_batch_completes
- Partial failure: test_partial_failure_one_error
Closes: miroir-uhj.11
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Integrates the existing tenant affinity module into the proxy request
handling to enable noisy-neighbor isolation for multi-tenant deployments.
Changes:
- Add TenantAffinityManager to AppState with initialization
- Resolve tenant identity from X-Miroir-Tenant header in search handler
- Use pinned group for scatter planning when tenant affinity is active
- Session pin takes precedence over tenant affinity (plan §13.15 interaction)
- Add miroir_tenant_session_pin_override_total metric
- Fix tenant affinity tests to be robust against hash value variations
Tenant affinity modes:
- header: read tenant ID from X-Miroir-Tenant header
- api_key: derive tenant from API key via tenant_map table
- explicit: static map only, unknown tenants use fallback policy
Writes always fan out to all groups (consistency invariant).
Only reads honor tenant affinity for isolation.
Metrics: miroir_tenant_queries_total, miroir_tenant_pinned_groups,
miroir_tenant_fallback_total, miroir_tenant_session_pin_override_total
Closes: miroir-uhj.15
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds Prometheus metrics for resharding operations (plan §13.1):
- miroir_reshard_in_progress: gauge for active operations
- miroir_reshard_phase: gaugeVec tracking current phase per index
- miroir_reshard_documents_backfilled_total: counterVec for backfilled docs
- miroir_reshard_cleanup_completed_seconds: histogram for cleanup duration
The cleanup_phase function in reshard.rs was already implemented,
but the metrics integration was missing. This commit adds the
metrics definition, initialization, accessor methods, and tests.
Accepts cleanup metrics callback in cleanup_phase() for emitting
miroir_reshard_cleanup_completed_seconds gauge as specified in
bead miroir-uhj.1.6.
Closes: miroir-uhj.1.6
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Fix cdc.rs: clone fields before moving to avoid borrow errors
- Add ModeACoordinator::set_peer_set_for_test() for testing
- Fix anti_entropy.rs tests to use new test-only method
- Add DriftRepairCallback type and with_metrics_callback() to DriftReconciler
- Wire up drift reconciler metrics to inc_settings_drift_repair()
The drift reconciler now properly records metrics when repairing
settings drift across nodes (plan §13.5).
Closes: miroir-uhj.5.4
- Implement iframe mode (?embed=true) that strips chrome and sends postMessage events for height auto-resize and result-clicked
- Implement headless mode (?headless=true) that returns only results container without search input or facets
- Add web component widget (/ui/widget.js) that registers <miroir-search> custom element with index and accent attributes
- Add custom template support (result_template: custom) with Handlebars-style interpolation ({{field}}, {{#if}}...{{/if}})
- Templates stored in search_ui_config table via task_store, with validation and error handling
- UI falls back to default card template on custom template errors
- Add GET /_miroir/ui/search/{index}/config endpoint to retrieve stored configuration
Closes: miroir-uhj.21.5
Implements rate limiting and exponential backoff for admin login:
- 10 requests per minute per IP (configurable via admin_ui.rate_limit.per_ip)
- Exponential backoff after 5 consecutive failed attempts: 10m, 20m, 40m, ... up to 24h cap
- Successful login resets both rate limit counter and backoff state
- Uses Redis backend with keys miroir:ratelimit:adminlogin:<ip> and miroir:ratelimit:adminlogin:backoff:<ip>
Also updates documentation to reflect the new rate limiting behavior.
The rate limiting logic was already implemented in RedisTaskStore
(check_rate_limit_admin_login, record_failure_admin_login, reset_rate_limit_admin_login)
but was not being used by the admin_login handler in session.rs.
Closes: miroir-uhj.19.5
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>