@goodandready/dsh-key-rotation 0.8.11 → 0.8.13

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,196 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@goodandready/dsh-key-rotation` are documented here.
4
+ User-facing feature notes also appear in README (en is source of truth).
5
+
6
+ ## 0.8.13 - 2026-09-17
7
+
8
+ ### Changed
9
+ - **Server Decomposition (#312)**: `lib/index.js` decomposed from 924 to ~562 lines to meet the 600-line plugin authoring threshold. Extracted modular services into `lib/logger.js`, `lib/sandbox-service.js`, `lib/budget-monitor.js`, `lib/pool-builder.js`, and `lib/lifecycle.js`. Public exports and runtime behavior are 100% preserved.
10
+ - **Logging Alignment**: replaced all runtime `console.warn` calls across server modules (`lib/index.js`, `lib/rotate.js`, `lib/ops-status.js`, `lib/ops-webhook.js`) with Cordis scoped logger (`ctx.logger('key-rotation')` / `getLogger(ctx)`).
11
+
12
+ ### Fixed
13
+ - **Preflight & Theme Conformance (#312)**:
14
+ - Client UI: eliminated 11 hardcoded fallback hex color literals from `lib/client.js`, strictly adopting `--dsw-alias-state-*` tokens.
15
+ - Client UI: requested `IconChevronDownOutline14` in module header to satisfy preflight icon contract.
16
+ - HTTP Bridge: added strict HTTP method guard (`GET`, `PUT`, `DELETE`, `OPTIONS`) on `/api/dsh-key-rotation/config` write-capable routes.
17
+ - Preflight score improved to `FAIL: 0, WARN: 1` (only `lib/client.js` exceeding 600 lines as documented single-file browser bundle).
18
+
19
+ ### Verification
20
+ - unit: 375 pass / 0 fail / 1 skipped
21
+ - `bash preflight.sh`: FAIL: 0, WARN: 1
22
+
23
+ ## 0.8.12 - 2026-09-16
24
+
25
+ ### Changed
26
+ - **Internal decompose (#312)**: operational HTTP routes split from a single `lib/routes-ops.js` facade into focused modules (`lib/ops-status.js`, `ops-telemetry.js`, `ops-keys.js`, `ops-test.js`, `ops-webhook.js`, shared `lib/ops-paths.js`). Public entry `registerOpsRoutes` is unchanged.
27
+ - Notify helpers (`notifySwitch`, `pushEvent`, `notifyExhaustion`) extracted to `lib/notify-events.js`; `lib/index.js` re-exports the public pair.
28
+ - `lib/client.js` intentionally remains a single ModuleLoader factory (no bundler contract); pure helpers stay in `lib/client-helpers.js`.
29
+
30
+ ### Fixed
31
+ - `stability-hardening` inject mock now provides `sctx.get` so the settings registration path runs when the schemastery peer is present.
32
+
33
+ ### Notes
34
+ - No user-facing behavior change: same HTTP paths, same rotation/notify semantics, same settings card.
35
+ - `lib/index.js` `apply()` remains a single cordis wiring unit by design.
36
+
37
+ ### Packaging
38
+ - New runtime modules under `lib/` are included via existing `files: ["lib", ...]`.
39
+
40
+ ### Verification
41
+ - unit: 377 pass / 0 fail
42
+ - PR #319 merged to main (`4d78853`)
43
+
44
+ ## 0.8.11 - 2026-09-16
45
+
46
+ ### Added
47
+ - **One-click plugin updater** (#307): host endpoint `GET/POST /api/dsh-key-rotation/update` and settings UI section to check the latest npm version and install it via the standard `dsh plugin add` path. POST is loopback + same-origin gated; GET exposes version metadata only.
48
+ - `lib/best-effort.js` helper for intentional non-critical side effects (sync + async, debug log, never throws) (#315).
49
+ - `dsh.client.inject` declares `@deepseek-ai/dsh-client-locale` and `@deepseek-ai/dsh-client-ui-settings` (#313).
50
+ - Unit tests: `test/plugin-updater-307.test.mjs`, `test/best-effort.test.mjs`.
51
+
52
+ ### Changed
53
+ - Client styles use theme tokens via `color-mix` / `--krot_chart-*` instead of hard-coded rgba/hex (#311).
54
+ - Cyrillic comments in `lib/client.js` translated to English; locale note documents en+zh (#309).
55
+ - DESIGN.md locks: ModuleLoader single-file client constraint; short cordis `export const name` vs scoped npm identity; bestEffort policy (#312, #315).
56
+
57
+ ### Fixed
58
+ - Empty `catch` blocks that swallowed persistence, circuit-reset, webhook-callback and UI errors now go through `bestEffort` with debug logging (#315).
59
+ - Production-path tests retargeted after removal of dead exports (`isSoftFailure`, `TokenBucketAccumulator`, etc.) and `test/bucket-o1.test.mjs` (#314).
60
+
61
+ ### Removed
62
+ - Internal agent files (`AGENTS.md`, `index.md`, `docs/plans/*`) from the git publication set and npm pack (#308).
63
+ - Stale release `.tgz` artifacts from the DEV tree (#310).
64
+
65
+ ### Packaging
66
+ - `npm pack`: 34 files, max file ~105KB (`lib/client.js`), no AGENTS/index/docs/plans/openwiki.
67
+
68
+ ### Verification
69
+ - unit: 248 pass / 0 fail / 1 skip
70
+ - preflight: empty-catch FAIL cleared; remaining name FAIL is accepted short cordis id (image-gen convention)
71
+ - MiniPC test contour: status 200, updater GET 200, POST foreign/missing origin 403, cleanup OK
72
+ - Production candidate (temporary tgz): status 200, providers=2 keys=7 present=7, updater GET 200, POST 403 on non-local origin
73
+
74
+ ## 0.8.8 - 2026-09-12
75
+
76
+ - feat(ui): add dedicated `.krot-header` with live status badges (pools count, keys configured, circuit state, health score) and subtitle matching `dsh-clinebot` styling (#301, #302)
77
+ - fix(ops): resolve `ReferenceError: quotaStore is not defined` in `GET /dsh-key-rotation/health` by passing dependency from `index.js` and adding defensive invocation (#301, #302)
78
+ - fix(ops): import `isLoopbackAddress` in `routes-ops.js` for local loopback verification without Origin header (#301, #302)
79
+ - fix(ops): guard `lastTestCache.snapshot` in `GET /dsh-key-rotation/sandbox-cache` against Map and mock instances (#301, #302)
80
+ - fix(ops): synchronize provider reset in `POST /dsh-key-rotation/webhook-action` with `RESET_PATH` to clear `authFailCounts`, reset `switches`, and reset `circuitBreaker` (#301, #302)
81
+ - fix(ops): single-key reset in `POST /dsh-key-rotation/reset` now checks and clears `brokenUntil` and `authFailCounts` even when cooldown has elapsed (#301, #302)
82
+ - fix(ops): clean up orphan entries from `poolState` and `lastTestCache` upon key deletion via `DELETE /dsh-key-rotation/key` (#301, #302)
83
+ - test: add comprehensive test suites `test/routes-ops-comprehensive.test.mjs` (12 tests) and `test/http-bridge-config.test.mjs` (5 tests), increasing overall test coverage to 89.40% (359 total unit tests) (#301, #302)
84
+
85
+ ## 0.8.7 - 2026-09-12
86
+
87
+ - fix(client): resolve `t is not defined` ReferenceError during client bundle factory loading (#285)
88
+ - test: add automated VM execution test for client module loader factory and exports (`test/client-factory-import.test.mjs`)
89
+
90
+ ## 0.8.6 - 2026-09-11
91
+
92
+ - feat(ops): add e2e failover harness covering multi-key pool exhaustion and recovery (#285)
93
+ - feat(ops): add chaos concurrency tests for parallel rotate/cooldown/reset races (#286)
94
+ - feat(ops): persist pool and circuit-breaker state across restarts via `lib/persistence.js` (#287)
95
+ - fix(ops): resolve persistence path from `persistencePath` → `DSH_HOME` → `cwd`; do not read `ctx.baseDir` (#287, #299)
96
+ - feat(ops): sanitize status snapshots — key values never leave the host (#288)
97
+ - feat(ui): accessible modal (dialog role, focus trap, Escape, focus restore) (#289)
98
+ - feat(ui): loading/error/unavailable/empty card states (#290)
99
+ - feat(ui): keyboard navigation and bulk key removal with confirmation (#291)
100
+ - fix(locale): complete en locale coverage for new UI strings (#292)
101
+ - docs: refresh `index.md` coverage matrix and pack policy (#293, #296)
102
+ - docs: document ModuleLoader single-file client constraint and helper split (#294)
103
+ - chore: branch/worktree hygiene audit recorded (#295)
104
+
105
+ ## 0.8.5 - 2026-09-10
106
+
107
+ - feat(ui): add interactive key load distribution bar chart (`.krot-load-chart`, `.krot-load-bar`, `.krot-load-segment`) displaying proportional request volume per key (#283, #284)
108
+ - feat(ui): add modal action confirmation dialog (`.krot-modal`, `.krot-modal-card`) for pool cooldown resets and provider/key deletions (#283, #284)
109
+ - fix(ops): activate jitter (`applyJitter`) on 429 and 5xx backoff calculations in `lib/rotate.js` and `lib/index.js` to eliminate the thundering herd retry spike (#283, #284)
110
+ - fix(ops): synchronously reset provider `circuitBreaker` on `POST /dsh-key-rotation/reset` ops route (#283, #284)
111
+ - test: add dedicated Phase 2 test suite `test/stability-phase2-283.test.mjs` (313 total unit tests passing) (#283, #284)
112
+
113
+ ## 0.8.4 - 2026-09-10
114
+
115
+ - fix(ui): inject `settingsScope` in client manifest and safely guard context property access to prevent ErrorBoundary crash on card mount (#281)
116
+
117
+ ## 0.8.3 - 2026-09-10
118
+
119
+ - feat(ui): unify settings card styling with `dsh-clinebot` design baseline (`.krot-section-card`, `.krot-stat-box`, `.krot-badge-ok/warn/bad`, `.krot-btn-primary/danger`, 36px inputs with brand focus, `data-dsh-plugin="dsh-key-rotation"` style isolation) (#281, #282)
120
+ - feat(ui): add live pool health telemetry stat boxes (configured pools, total keys, healthy/ready keys) (#281)
121
+ - feat(ui): replace hardcoded hex colors with semantic DSH design system tokens (`--dsw-alias-state-*`, `--dsw-alias-bg-*`) for full dark/light theme fidelity (#281)
122
+ - test: add comprehensive stability coverage test suite (`test/stability-coverage-281.test.mjs`, 18 new unit tests covering quota windows, clock monotonicity, sandbox cache, pool network guards, and http bridge helpers) (#281)
123
+
124
+ ## 0.8.2 - 2026-09-10
125
+
126
+ - fix(ui): settings live only as `settings.plugin.item` card — fallback `settings.section` sidebar row removed (#275)
127
+ - fix: resolve DSH services via `ctx.get('llm')` / `sctx.get('settings')` instead of bare context properties that proxy to `undefined` (#275)
128
+ - fix(locale): source strings are English-only (`ctx.locale.register(NS, { en })`); other languages come from host `props.t` / translation plugins (#277, GitHub #1)
129
+ - fix(locale): active locale falls back `ctx.locale` snapshot → first `navigator.languages` entry → `en` (core-aligned) (#277)
130
+ - test: card-only, `ctx.get` and full Cyrillic-in-literal locale gates
131
+
132
+ ## 0.8.1 - 2026-09-09
133
+
134
+ - fix(ui): settings card no longer disappears on open — ErrorBoundary around the section and referentially stable `settingsScope.getSnapshot()` (#273)
135
+
136
+ ## 0.8.0 - 2026-09-09
137
+
138
+ Stability block (#260–#270). Changed in v0.8.0.
139
+
140
+ - feat: per-provider **circuit breaker** (`circuitBreakerEnabled`, threshold/openMs/halfOpenProbes) — fail fast while a provider is down (#260)
141
+ - fix: **monotonic process clock** (`performance.timeOrigin + performance.now`) for cooldowns/durations so NTP jumps do not corrupt remaining times (#261)
142
+ - perf: **BoundedMap** (max + TTL + LRU) foundation for usage/notify maps (#262)
143
+ - fix: **non-blocking webhook notify** via bounded NotifyQueue with backoff — rotate() never awaits webhook I/O (#263)
144
+ - feat: **atomic file I/O helpers** (`atomicWriteFile`/`safeReadJson`/`safeParseJson`) — corrupt JSON never wipes previous state (#264)
145
+ - fix: in-stream failover remains safe across reload; `quotaStore` properly injected into rotate (#265)
146
+ - feat: **clone-route GC** — `expectedClones` computed from live providers; orphans dropped from runtime set (#266)
147
+ - test: **error taxonomy table** — 408/425/429/5xx, ECONNRESET/ETIMEDOUT, gRPC codes classified switch|surface|cooldown (#267)
148
+ - fix: status API returns **single snapshot** with `circuit`, `meta.expectedClones`, `meta.notifyQueue` (#268)
149
+ - test: **smoke harness** `test/smoke-rotation-080.test.mjs` — mock 429 → key switch → success (#269)
150
+ - docs: full 0.8.0 documentation package (README en/ru/zh config tables, DESIGN taxonomy, index test matrix) (#270)
151
+
152
+ ## 0.7.40 - 2026-09-09
153
+
154
+ - docs: align README en/ru/zh and DESIGN.md with shipped surface after v0.7.36 de-bloat (#251)
155
+ - docs: add project `index.md` and `AGENTS.md` (#252)
156
+ - refactor: split `lib/index.js` into `rotate.js`, `http-bridge.js`, `routes-ops.js` (#253)
157
+ - feat: `verboseLogging` config (default false) gates per-request rotation logs (#254)
158
+ - docs: CHANGELOG history and task_plan hygiene (#255)
159
+
160
+ ## 0.7.39 - 2026-08-28
161
+
162
+ - fix(heal): wire `lastUsedAt` into `credentials.resolve` and idle self-heal sweep; cache sweep runtime (issue #249, PR #250)
163
+ - perf: fewer redundant `buildRuntime()` calls on hot path
164
+
165
+ ## 0.7.38 - 2026-08-28
166
+
167
+ - perf(hotpath): single-pass `extractRateLimit`, memoized `todayIso`, zero-allocation status totals, purge stale notify maps (issue #246)
168
+
169
+ ## 0.7.37 - 2026-08-28
170
+
171
+ - fix(concurrency): AsyncLocalStorage request isolation for `pickedRef`; stream-exception failover before first token; copy `attemptList`; wire 30-day usage compaction; clear quarantine after successful sandbox probe (issue #243)
172
+
173
+ ## 0.7.36 - 2026-08-27
174
+
175
+ - refactor: remove overengineered modules (`shadow`, `incident`, `agent-budget`, `region`, `canary`, `maintenance`)
176
+ - perf: memoize `buildRuntime()`, atomic pointer round-robin, HTTP/gRPC error detection, exhaustion ETA, smart visibility polling
177
+
178
+ ## 0.7.34 - 2026-08-27
179
+
180
+ - fix(authoring): lifecycle dispose for credentials.resolve / stream listeners; secret role masking; settingsScope integration (PR #241)
181
+
182
+ ## 0.7.33 - 2026-08-27
183
+
184
+ - fix: key probing baseURL via pool owner; cascade recursion guard; midnight PST sign; timer cleanup via cordis effect; stale lock recovery in least-loaded balancer; zh locale
185
+
186
+ ## 0.7.31 - 2026-08-27
187
+
188
+ - perf: O(1) token bucket; soft vs hard backoff; penalty decay; cooldown jitter; canary probing (later removed in 0.7.36); TTFT percentiles; webhook digest; 30-day usage compaction; optimistic UI + filter pills
189
+
190
+ ## 0.7.20 - 2026-08-27
191
+
192
+ - fix(client): hoist `h` to factory scope for KeyRotationCard (#155)
193
+
194
+ ## Earlier
195
+
196
+ See Gitea release history and README changelog sections for pre-0.7.20 work.
@@ -0,0 +1,123 @@
1
+ // lib/budget-monitor.js — periodic budget, expiry, low health and SLO alerts
2
+ import { expiringSoon, shouldNotifyDaily, costForDay, costForWeek, budgetVerdict } from './pool.js';
3
+
4
+ const DAY_MS = 86400000;
5
+
6
+ export function checkBudgetAndHealthAlerts({
7
+ runtime,
8
+ poolState,
9
+ now,
10
+ expiryNotifiedAt,
11
+ budgetNotifiedAt,
12
+ lowHealthNotifiedAt,
13
+ sloNotifiedAt,
14
+ latencyHistogram,
15
+ webhookSender,
16
+ logger,
17
+ }) {
18
+ try {
19
+ const seen = new Set();
20
+ for (const pool of runtime.poolByRef.values()) {
21
+ if (seen.has(pool.base)) continue;
22
+ seen.add(pool.base);
23
+ // #207: keys expiring within expiryWarnDays -> one webhook per key/day
24
+ for (const { ref, expiresInDays } of expiringSoon(pool, runtime.expiryWarnDays, now)) {
25
+ if (!shouldNotifyDaily(expiryNotifiedAt, pool.base + ':' + ref, now)) continue;
26
+ logger?.warn?.(`[dsh-key-rotation] ${pool.base}: key ${ref} expires in ~${expiresInDays}d`);
27
+ if (runtime.notifyWebhook) {
28
+ webhookSender.send(runtime.notifyWebhook, {
29
+ title: `Key expiring soon: ${pool.base}`,
30
+ text: `${ref} expires in ~${expiresInDays} day(s)`,
31
+ provider: pool.base,
32
+ kind: 'expiry',
33
+ keys: [ref],
34
+ });
35
+ }
36
+ }
37
+ // #208: daily/weekly budget -> warn webhook, optional 1-day pause at 100%
38
+ const budget = runtime.providerBudgets.get(pool.base);
39
+ if (!budget) continue;
40
+ const daily = costForDay(pool.state.costDays);
41
+ const weekly = costForWeek(pool.state.costDays, now);
42
+ const verdict = budgetVerdict(daily, budget.costBudgetDaily);
43
+ const wVerdict = budgetVerdict(weekly, budget.costBudgetWeekly);
44
+ const hit = verdict.warn || wVerdict.warn;
45
+ if (hit && shouldNotifyDaily(budgetNotifiedAt, pool.base + ':budget', now)) {
46
+ logger?.warn?.(`[dsh-key-rotation] ${pool.base}: cost budget - day $${daily.toFixed(2)}/$${budget.costBudgetDaily} week $${weekly.toFixed(2)}/$${budget.costBudgetWeekly}`);
47
+ if (runtime.notifyWebhook) {
48
+ // #217: budget webhook gains action buttons when a callback token is configured
49
+ const token = runtime.webhookActionToken ?? '';
50
+ webhookSender.send(runtime.notifyWebhook, {
51
+ title: `Cost budget: ${pool.base}`,
52
+ text: `day $${daily.toFixed(2)} of $${budget.costBudgetDaily} · week $${weekly.toFixed(2)} of $${budget.costBudgetWeekly}` + (verdict.exceeded || wVerdict.exceeded ? ' · EXCEEDED' : ''),
53
+ provider: pool.base,
54
+ kind: 'budget',
55
+ spend: { daily, weekly },
56
+ actionToken: token || undefined,
57
+ actions: token ? [
58
+ { id: `pause-${pool.base}`, label: 'Pause 1h' },
59
+ { id: `reset-${pool.base}`, label: 'Reset cooldown' },
60
+ ] : undefined,
61
+ });
62
+ }
63
+ }
64
+ if ((verdict.exceeded || wVerdict.exceeded) && budget.pauseOnBudget) {
65
+ const until = now + DAY_MS;
66
+ for (const ref of pool.refs) {
67
+ if ((pool.state.failedUntil.get(ref) ?? 0) < until) pool.state.failedUntil.set(ref, until);
68
+ }
69
+ }
70
+ // #221: pool running low - webhook while healthy < warnBelowHealthy
71
+ const warnBelow = runtime.warnBelowHealthy ?? 0;
72
+ if (warnBelow > 0) {
73
+ let healthy = 0;
74
+ for (const ref of pool.refs) {
75
+ const fu = pool.state.failedUntil.get(ref);
76
+ if (fu !== undefined && fu > now) continue;
77
+ const exp = pool.expiresAt?.[ref];
78
+ if (exp !== undefined && now >= exp) continue;
79
+ healthy++;
80
+ }
81
+ if (healthy < warnBelow && shouldNotifyDaily(lowHealthNotifiedAt, pool.base, now)) {
82
+ logger?.warn?.(`[dsh-key-rotation] ${pool.base}: pool running low - ${healthy}/${pool.refs.length} healthy`);
83
+ if (runtime.notifyWebhook) {
84
+ const token = runtime.webhookActionToken ?? '';
85
+ webhookSender.send(runtime.notifyWebhook, {
86
+ title: `Pool running low: ${pool.base}`,
87
+ text: `${healthy}/${pool.refs.length} keys healthy (alert below ${warnBelow})`,
88
+ provider: pool.base,
89
+ kind: 'low-health',
90
+ healthy,
91
+ total: pool.refs.length,
92
+ actionToken: token || undefined,
93
+ actions: token ? [{ id: `reset-${pool.base}`, label: 'Reset cooldown' }] : undefined,
94
+ });
95
+ }
96
+ }
97
+ }
98
+ // #225: latency SLO - webhook when a key's p95 exceeds the threshold
99
+ const slo = runtime.latencySloMs ?? 0;
100
+ if (slo > 0) {
101
+ for (const ref of pool.refs) {
102
+ const snap = latencyHistogram.snapshot(ref);
103
+ if (!snap.p95 || snap.p95 <= slo) continue;
104
+ if (!shouldNotifyDaily(sloNotifiedAt, pool.base + ':' + ref + ':slo', now)) continue;
105
+ logger?.warn?.(`[dsh-key-rotation] ${pool.base}: ${ref} p95 ${Math.round(snap.p95)}ms > SLO ${slo}ms`);
106
+ if (runtime.notifyWebhook) {
107
+ webhookSender.send(runtime.notifyWebhook, {
108
+ title: `Latency SLO exceeded: ${pool.base}`,
109
+ text: `${ref} p95 ${Math.round(snap.p95)}ms > ${slo}ms (${snap.count} samples)`,
110
+ provider: pool.base,
111
+ kind: 'latency-slo',
112
+ ref,
113
+ p95: Math.round(snap.p95),
114
+ slo,
115
+ });
116
+ }
117
+ }
118
+ }
119
+ }
120
+ } catch (_) {
121
+ /* maintenance must never crash the sweep */
122
+ }
123
+ }
package/lib/client.js CHANGED
@@ -21,6 +21,15 @@ window.__ModuleLoader__.load({
21
21
  const React = require('react');
22
22
  const h = React.createElement;
23
23
 
24
+ // Core primitives and chevron icon
25
+ let ChevronIcon = null;
26
+ try {
27
+ const primitives = require('@deepseek-ai/dsh-client-primitives') || require('@deepseek-ai/dsh-client-icons');
28
+ ChevronIcon = primitives && (primitives.IconChevronDownOutline14 || primitives.IconChevronDownOutline);
29
+ } catch (err) {
30
+ ChevronIcon = null;
31
+ }
32
+
24
33
  const CONFIG_PATH = '/dsh-key-rotation/config';
25
34
  const NS = 'dsh-key-rotation';
26
35
 
@@ -413,7 +422,7 @@ window.__ModuleLoader__.load({
413
422
  '.krot-page-sub{font-size:13px;color:var(--dsw-alias-label-secondary);line-height:1.5}',
414
423
  '.krot p{margin:0}',
415
424
  '.krot-hint{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.4}',
416
- '.krot-err{font-size:13px;color:var(--dsw-alias-state-error-primary);padding:10px 14px;border-radius:8px;background:color-mix(in srgb, var(--dsw-alias-state-error-primary, #ef4444) 10%, transparent);display:flex;align-items:center;gap:8px}',
425
+ '.krot-err{font-size:13px;color:var(--dsw-alias-state-error-primary);padding:10px 14px;border-radius:8px;background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);display:flex;align-items:center;gap:8px}',
417
426
  '.krot-label{font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary)}',
418
427
  '.krot-field{display:flex;flex-direction:column;gap:6px}',
419
428
  '.krot-in{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;height:36px;font-family:inherit;min-width:0;width:100%;box-sizing:border-box;transition:border-color .15s ease}',
@@ -436,15 +445,15 @@ window.__ModuleLoader__.load({
436
445
  '.krot-load-legend{display:flex;flex-wrap:wrap;gap:8px;margin-top:6px;font-size:11px}',
437
446
  '.krot-load-item{display:inline-flex;align-items:center;gap:4px}',
438
447
  '.krot-load-dot{width:7px;height:7px;border-radius:50%}',
439
- '.krot-modal-backdrop{position:fixed;top:0;left:0;right:0;bottom:0;background:var(--dsw-alias-bg-overlay, color-mix(in srgb, var(--dsw-alias-bg-layer-1, #000) 55%, transparent));z-index:9999;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(2px)}',
440
- '.krot-modal-card{width:90%;max-width:420px;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;box-shadow:0 12px 36px color-mix(in srgb, var(--dsw-alias-bg-layer-1, #000) 28%, transparent);padding:20px;display:flex;flex-direction:column;gap:12px}',
448
+ '.krot-modal-backdrop{position:fixed;top:0;left:0;right:0;bottom:0;background:var(--dsw-alias-bg-overlay, color-mix(in srgb, var(--dsw-alias-bg-layer-1) 55%, transparent));z-index:9999;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(2px)}',
449
+ '.krot-modal-card{width:90%;max-width:420px;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;box-shadow:0 12px 36px color-mix(in srgb, var(--dsw-alias-bg-layer-1) 28%, transparent);padding:20px;display:flex;flex-direction:column;gap:12px}',
441
450
  '.krot-modal-title{font-size:16px;font-weight:600;color:var(--dsw-alias-label-primary)}',
442
451
  '.krot-modal-desc{font-size:13px;color:var(--dsw-alias-label-secondary);line-height:1.45}',
443
452
  '.krot-modal-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:8px}',
444
453
  '.krot-badge{font-size:12px;padding:3px 10px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);display:inline-flex;align-items:center;gap:5px;font-weight:500;white-space:nowrap}',
445
- '.krot-badge-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary, #10b981) 8%, transparent)}',
446
- '.krot-badge-warn{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:color-mix(in srgb, var(--dsw-alias-state-warning-primary, #f59e0b) 8%, transparent)}',
447
- '.krot-badge-bad{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary, #ef4444) 8%, transparent)}',
454
+ '.krot-badge-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 8%, transparent)}',
455
+ '.krot-badge-warn{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:color-mix(in srgb, var(--dsw-alias-state-warning-primary) 8%, transparent)}',
456
+ '.krot-badge-bad{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent)}',
448
457
  '.krot-prov{display:flex;flex-direction:column;gap:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2);padding:14px 16px}',
449
458
  '.krot-prov-head{display:flex;gap:10px;align-items:center;flex-wrap:wrap}',
450
459
  '.krot-prov-head select{flex:1;min-width:180px}',
@@ -462,18 +471,18 @@ window.__ModuleLoader__.load({
462
471
  '.krot-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-4,var(--dsw-alias-bg-layer-2));border-color:var(--dsw-alias-label-dimmed,var(--dsw-alias-border-l2))}',
463
472
  '.krot-btn-primary,.krot-save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:transparent;font-weight:600}',
464
473
  '.krot-btn-primary:hover:not(:disabled),.krot-save:hover:not(:disabled){background:var(--dsw-alias-label-primary)!important;color:var(--dsw-alias-bg-layer-3)!important;opacity:0.88}',
465
- '.krot-btn-danger{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb, var(--dsw-alias-state-error-primary, #ef4444) 30%, transparent)}',
466
- '.krot-btn-danger:hover:not(:disabled){background:color-mix(in srgb, var(--dsw-alias-state-error-primary, #ef4444) 12%, transparent)!important;border-color:color-mix(in srgb, var(--dsw-alias-state-error-primary, #ef4444) 50%, transparent)}',
474
+ '.krot-btn-danger{color:var(--dsw-alias-state-error-primary);border-color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 30%, transparent)}',
475
+ '.krot-btn-danger:hover:not(:disabled){background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent)!important;border-color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 50%, transparent)}',
467
476
  '.krot-btn:disabled{opacity:0.45;cursor:not-allowed}',
468
477
  '.krot-foot{display:flex;gap:10px;align-items:center;flex-wrap:wrap}',
469
478
  '.krot-filter-bar{display:flex;gap:8px;margin:4px 0 10px;flex-wrap:wrap}',
470
479
  '.krot-pill{appearance:none;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:4px 12px;font-size:12px;font-weight:500;color:var(--dsw-alias-label-secondary);cursor:pointer;transition:all .15s ease}',
471
480
  '.krot-pill:hover{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}',
472
481
  '.krot-pill-active{background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)!important;font-weight:600}',
473
- '.krot-pill-warn{border-color:color-mix(in srgb, var(--dsw-alias-state-warning-primary, #f59e0b) 30%, transparent);color:var(--dsw-alias-state-warning-primary)}',
474
- '.krot-pill-err{border-color:color-mix(in srgb, var(--dsw-alias-state-error-primary, #ef4444) 30%, transparent);color:var(--dsw-alias-state-error-primary)}',
475
- '.krot-alert-ok{padding:10px 14px;border-radius:8px;background:color-mix(in srgb, var(--dsw-alias-state-success-primary, #10b981) 10%, transparent);color:var(--dsw-alias-state-success-primary);font-size:13px;display:flex;align-items:center;gap:10px}',
476
- '.krot-alert-bad{padding:10px 14px;border-radius:8px;background:color-mix(in srgb, var(--dsw-alias-state-error-primary, #ef4444) 10%, transparent);color:var(--dsw-alias-state-error-primary);font-size:13px;display:flex;align-items:center;gap:10px}',
482
+ '.krot-pill-warn{border-color:color-mix(in srgb, var(--dsw-alias-state-warning-primary) 30%, transparent);color:var(--dsw-alias-state-warning-primary)}',
483
+ '.krot-pill-err{border-color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 30%, transparent);color:var(--dsw-alias-state-error-primary)}',
484
+ '.krot-alert-ok{padding:10px 14px;border-radius:8px;background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 10%, transparent);color:var(--dsw-alias-state-success-primary);font-size:13px;display:flex;align-items:center;gap:10px}',
485
+ '.krot-alert-bad{padding:10px 14px;border-radius:8px;background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);color:var(--dsw-alias-state-error-primary);font-size:13px;display:flex;align-items:center;gap:10px}',
477
486
  '.krot-event-stream{display:flex;flex-direction:column;gap:6px;margin-top:8px;padding:10px 12px;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:8px}',
478
487
  '.krot-event-row{display:flex;align-items:center;gap:8px;font-size:12px;padding:4px 0;border-bottom:1px solid var(--dsw-alias-border-l1)}',
479
488
  '.krot-event-time{font-family:ui-monospace,Menlo,Consolas,monospace;color:var(--dsw-alias-label-tertiary);font-size:11px}',
@@ -487,7 +496,7 @@ window.__ModuleLoader__.load({
487
496
  '.krot-card-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:16px 0 8px}',
488
497
  '.krot-header-chip{display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 10px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-size:12px;font-weight:600;cursor:pointer;position:relative;user-select:none;transition:all .15s ease}',
489
498
  '.krot-header-chip:hover{background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-3));border-color:var(--dsw-alias-border-l1);transform:translateY(-0.5px)}',
490
- '.krot-popover{position:absolute;top:calc(100% + 6px);right:0;z-index:10000;min-width:220px;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:12px 14px;box-shadow:0 16px 40px color-mix(in srgb, var(--dsw-alias-bg-layer-1, #000) 45%, transparent),0 2px 8px color-mix(in srgb, var(--dsw-alias-bg-layer-1, #000) 15%, transparent);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);display:flex;flex-direction:column;gap:8px;text-align:left}',
499
+ '.krot-popover{position:absolute;top:calc(100% + 6px);right:0;z-index:10000;min-width:220px;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:12px 14px;box-shadow:0 16px 40px color-mix(in srgb, var(--dsw-alias-bg-layer-1) 45%, transparent),0 2px 8px color-mix(in srgb, var(--dsw-alias-bg-layer-1) 15%, transparent);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);display:flex;flex-direction:column;gap:8px;text-align:left}',
491
500
  '.krot-pop-title{font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--dsw-alias-label-secondary)}',
492
501
  '.krot-pop-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:4px 0}',
493
502
  '.krot-pop-name{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;gap:8px}',