@goodandready/dsh-key-rotation 0.8.80 → 0.8.82

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 CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.82 (2026-10-30)
4
+
5
+ ### Modernized UI Builder & JSX Pipeline (Block 10 Release)
6
+ - **Modern JSX/TSX Build Pipeline via esbuild (Issue #523)**: Replaced mechanical text concatenation with modern compiler-based build pipeline (`scripts/build-client.mjs`) using `esbuild`. Supports native JSX syntax with `React.createElement` transpilation.
7
+ - **Component JSX Modernization**: Migrated key client components (such as `Sparkline` and `KeyRotationHeaderChip`) to idiomatic JSX, improving clarity and maintainability while preserving strict modular scoping and zero runtime overhead.
8
+ - **Deterministic & Compliant Bundle Assembly**: Automated atomic output writing with size verification (< 256 KiB DSH Store limit, actual: ~207 KiB), UTF-8 charset preservation for multi-language localization (en, zh, ru), inline legal comments, and syntax verification (`node --check`).
9
+ - **CI/CD Drift Prevention**: Built-in `--check` mode in `build-client.mjs` ensuring `lib/client.js` is always strictly in sync with `lib/client-src/`.
10
+
11
+ ## 0.8.81 (2026-10-30)
12
+
13
+ ### Direct Extension Pipeline & Pure Key Resolution (Block 9 Release)
14
+ - **Direct Extension Pipeline without Monkey-Patching (Issue #526)**: Introduced clean credential injection and direct resolution pipeline via `resolveKeyDirectly` (`lib/resolver.js`). Bypasses monkey-patching of global `credentials.resolve` and direct mutations on foreign services.
15
+ - **Explicit Stream Options Delivery (`lib/rotate.js`)**: Passes resolved secrets directly through `directOpts` (`apiKey`, `headers.Authorization`, `_rotationKeyRef`) into `llm.stream`, allowing provider adapters to consume credentials without touching global process state. Preserved `dispatchStorage` as backward-compatible fallback for legacy runtimes.
16
+ - **Dedicated Key Penalty Manager (`lib/rotate-penalty.js`)**: Extracted `penalizeKey` into dedicated module (<200 lines) with dynamic header rate-limit tracking, backoff computation, quota window synchronization, early warning notifications, and cross-pool permanent pause fan-out, maintaining `lib/rotate.js` well below the 600-line modularity limit.
17
+
18
+ ## 0.8.80 (2026-10-30)
19
+
20
+ ### Layered Core Decoupling, Append-Only Journal & Dynamic Cordis Injection (Block 8 Release)
21
+ - **Layered Core Architecture (Issue #530)**: Reorganized algorithmic core into `lib/core/` (`state-machine.js`, `concurrency.js`, `quota.js`, `pool.js`, `rotate.js`, `index.js`) with backwards-compatible top-level facades in `lib/*.js`.
22
+ - **Dynamic Cordis Injection (Issue #528)**: Decoupled optional dependencies via dynamic `ctx.inject(['webServer'], ...)`, minimizing static requirements to `['llm']` and ensuring smooth headless/CLI execution.
23
+ - **Append-Only Journal Persistence (Issue #525)**: Implemented incremental append-only journaling (`*-journal.jsonl`) in `StatePersistence`, eliminating O(N) full-disk state serialization on routine key cooldowns and rotation events with automated rotation upon `flush()`.
24
+
25
+ ## 0.8.79 (2026-10-30)
26
+
27
+ ### Modular Route Layer, Schemastery Volatile & Key State Machine (Block 7 Release)
28
+ - **Modular Route Structure (Issue #522)**: Extracted route handlers into dedicated `lib/routes/` directory (`status.js`, `keys.js`, `metrics.js`, `balance.js`, `telemetry.js`, `testing.js`, `webhook.js`, `config.js`, `quick-action.js`, `guard.js`, `paths.js`) with legacy compatibility facades in `lib/ops-*.js`.
29
+ - **Schemastery Volatile Config Migration (Issue #551)**: Implemented `unwrapVolatileConfig` and deprecation headers (`X-DSH-Deprecated`, `Deprecation`) on deprecated config endpoints.
30
+ - **Key State Machine (Issue #529)**: Introduced finite state machine `KeyStateMachine` (`lib/key-state-machine.js`) with strict transition matrix (`VALID_TRANSITIONS`), audit trail, and state statistics.
31
+
3
32
  ## 0.8.78 (2026-10-30)
4
33
 
5
34
  ### Network Transports, Model Catalog & Predictive Quota Runway (Block 6 Release)
@@ -1,7 +1,9 @@
1
1
  function Sparkline({ points = [], width = 120, height = 22, stroke = 'var(--dsw-alias-brand-primary, currentColor)' }) {
2
2
  if (!Array.isArray(points) || points.length < 2) {
3
- return h('svg', { width, height, viewBox: `0 0 ${width} ${height}`, style: { opacity: 0.3 } },
4
- h('line', { x1: 0, y1: height / 2, x2: width, y2: height / 2, stroke, strokeWidth: 1, strokeDasharray: '3 3' })
3
+ return (
4
+ <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} style={{ opacity: 0.3 }}>
5
+ <line x1={0} y1={height / 2} x2={width} y2={height / 2} stroke={stroke} strokeWidth={1} strokeDasharray="3 3" />
6
+ </svg>
5
7
  );
6
8
  }
7
9
  const min = Math.min(...points);
@@ -14,19 +16,23 @@
14
16
  return `${x},${y}`;
15
17
  }).join(' ');
16
18
 
17
- return h('svg', {
18
- width,
19
- height,
20
- viewBox: `0 0 ${width} ${height}`,
21
- style: { display: 'inline-block', verticalAlign: 'middle', overflow: 'visible' },
22
- }, h('polyline', {
23
- points: coords,
24
- fill: 'none',
25
- stroke,
26
- strokeWidth: 2,
27
- strokeLinecap: 'round',
28
- strokeLinejoin: 'round',
29
- }));
19
+ return (
20
+ <svg
21
+ width={width}
22
+ height={height}
23
+ viewBox={`0 0 ${width} ${height}`}
24
+ style={{ display: 'inline-block', verticalAlign: 'middle', overflow: 'visible' }}
25
+ >
26
+ <polyline
27
+ points={coords}
28
+ fill="none"
29
+ stroke={stroke}
30
+ strokeWidth={2}
31
+ strokeLinecap="round"
32
+ strokeLinejoin="round"
33
+ />
34
+ </svg>
35
+ );
30
36
  }
31
37
 
32
38
  function parseBulkKeys(text) {
@@ -339,7 +339,7 @@
339
339
  );
340
340
  })();
341
341
 
342
- // ── per-model sub-pools: keys + token quotas, plus live usage meters ──
342
+ /*! ── per-model sub-pools: keys + token quotas, plus live usage meters ── */
343
343
  const modelPools = (() => {
344
344
  const models = modelEntriesOf(entry);
345
345
  const statusFor = (model) => status[entry.provider + '::' + model] ?? null;
@@ -80,58 +80,66 @@
80
80
  const color = !poolEntries.length ? 'var(--dsw-alias-label-tertiary)' : anyExhausted ? 'var(--dsw-alias-state-error-primary)' : healthy < total ? 'var(--dsw-alias-state-warn-primary)' : 'var(--dsw-alias-state-success-primary)';
81
81
  const label = poolEntries.length ? `${healthy}/${total} rot` : 'rot';
82
82
 
83
- return h('div', { ref, style: { position: 'relative', display: 'inline-flex' } },
84
- h('button', {
85
- type: 'button',
86
- className: 'krot-header-chip',
87
- title: t('title'),
88
- onClick: () => setOpen((v) => !v),
89
- },
90
- h('span', { style: { width: '8px', height: '8px', borderRadius: '50%', background: color, flex: 'none', boxShadow: `0 0 6px ${color}` } }),
91
- label,
92
- h('span', { style: { fontSize: '9px', opacity: 0.6 } }, open ? '▲' : '▼')
93
- ),
94
- open ? h('div', { className: 'krot-popover', style: { minWidth: '220px' } },
95
- h('div', { className: 'krot-pop-title' }, t('headerPoolsTitle')),
96
- !poolEntries.length ? h('div', { style: { fontSize: '12px', color: 'var(--dsw-alias-label-tertiary)' } }, t('noActivePools')) : null,
97
- poolEntries.map(([name, p]) => {
98
- const c = p.exhausted ? 'var(--dsw-alias-state-error-primary)' : (p.healthy < p.total ? 'var(--dsw-alias-state-warn-primary)' : 'var(--dsw-alias-state-success-primary)');
99
- const provName = p.providerName || name;
100
- const tail = p.lastUsedTail;
101
- return h('div', { key: name, className: 'krot-pop-row', style: { display: 'flex', flexDirection: 'column', gap: '4px', padding: '6px 0', borderBottom: '1px solid var(--dsw-alias-border-subtle)' } },
102
- h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
103
- h('div', { className: 'krot-pop-name' },
104
- h('span', { style: { width: '7px', height: '7px', borderRadius: '50%', background: c, flex: 'none' } }),
105
- name,
106
- tail ? h('span', { style: { marginLeft: '6px', fontSize: '11px', color: 'var(--dsw-alias-label-tertiary)', fontFeatureSettings: '"tnum"', fontFamily: 'monospace' } }, `...${tail}`) : null
107
- ),
108
- h('div', { className: 'krot-pop-count', style: { color: c, fontWeight: 600 } }, `${p.healthy}/${p.total}`)
109
- ),
110
- h('div', { style: { display: 'flex', gap: '4px', marginTop: '2px' } },
111
- h('button', {
112
- type: 'button',
113
- disabled: busy,
114
- onClick: (e) => { e.stopPropagation(); runAction('skip', provName, p.lastUsed); },
115
- title: t('quickSkipTitle'),
116
- style: { padding: '2px 6px', fontSize: '11px', borderRadius: '4px', border: '1px solid var(--dsw-alias-border-subtle)', background: 'transparent', cursor: 'pointer', color: 'inherit' }
117
- }, `⏭️ ${t('quickSkip')}`),
118
- h('button', {
119
- type: 'button',
120
- disabled: busy,
121
- onClick: (e) => { e.stopPropagation(); runAction('reset', provName, p.lastUsed); },
122
- title: t('quickResetTitle'),
123
- style: { padding: '2px 6px', fontSize: '11px', borderRadius: '4px', border: '1px solid var(--dsw-alias-border-subtle)', background: 'transparent', cursor: 'pointer', color: 'inherit' }
124
- }, `🔄 ${t('quickReset')}`),
125
- h('button', {
126
- type: 'button',
127
- disabled: busy || !p.lastUsed,
128
- onClick: (e) => { e.stopPropagation(); runAction('pause', provName, p.lastUsed); },
129
- title: t('quickPauseTitle'),
130
- style: { padding: '2px 6px', fontSize: '11px', borderRadius: '4px', border: '1px solid var(--dsw-alias-border-subtle)', background: 'transparent', cursor: 'pointer', color: 'inherit' }
131
- }, `⏸️ ${t('quickPause')}`)
132
- )
133
- );
134
- })
135
- ) : null
83
+ return (
84
+ <div ref={ref} style={{ position: 'relative', display: 'inline-flex' }}>
85
+ <button
86
+ type="button"
87
+ className="krot-header-chip"
88
+ title={t('title')}
89
+ onClick={() => setOpen((v) => !v)}
90
+ >
91
+ <span style={{ width: '8px', height: '8px', borderRadius: '50%', background: color, flex: 'none', boxShadow: `0 0 6px ${color}` }} />
92
+ {label}
93
+ <span style={{ fontSize: '9px', opacity: 0.6 }}>{open ? '▲' : '▼'}</span>
94
+ </button>
95
+ {open ? (
96
+ <div className="krot-popover" style={{ minWidth: '220px' }}>
97
+ <div className="krot-pop-title">{t('headerPoolsTitle')}</div>
98
+ {!poolEntries.length ? (
99
+ <div style={{ fontSize: '12px', color: 'var(--dsw-alias-label-tertiary)' }}>{t('noActivePools')}</div>
100
+ ) : null}
101
+ {poolEntries.map(([name, p]) => {
102
+ const c = p.exhausted ? 'var(--dsw-alias-state-error-primary)' : (p.healthy < p.total ? 'var(--dsw-alias-state-warn-primary)' : 'var(--dsw-alias-state-success-primary)');
103
+ const provName = p.providerName || name;
104
+ const tail = p.lastUsedTail;
105
+ return (
106
+ <div key={name} className="krot-pop-row" style={{ display: 'flex', flexDirection: 'column', gap: '4px', padding: '6px 0', borderBottom: '1px solid var(--dsw-alias-border-subtle)' }}>
107
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
108
+ <div className="krot-pop-name">
109
+ <span style={{ width: '7px', height: '7px', borderRadius: '50%', background: c, flex: 'none' }} />
110
+ {name}
111
+ {tail ? <span style={{ marginLeft: '6px', fontSize: '11px', color: 'var(--dsw-alias-label-tertiary)', fontFeatureSettings: '"tnum"', fontFamily: 'monospace' }}>{`...${tail}`}</span> : null}
112
+ </div>
113
+ <div className="krot-pop-count" style={{ color: c, fontWeight: 600 }}>{`${p.healthy}/${p.total}`}</div>
114
+ </div>
115
+ <div style={{ display: 'flex', gap: '4px', marginTop: '2px' }}>
116
+ <button
117
+ type="button"
118
+ disabled={busy}
119
+ onClick={(e) => { e.stopPropagation(); runAction('skip', provName, p.lastUsed); }}
120
+ title={t('quickSkipTitle')}
121
+ style={{ padding: '2px 6px', fontSize: '11px', borderRadius: '4px', border: '1px solid var(--dsw-alias-border-subtle)', background: 'transparent', cursor: 'pointer', color: 'inherit' }}
122
+ >{`⏭️ ${t('quickSkip')}`}</button>
123
+ <button
124
+ type="button"
125
+ disabled={busy}
126
+ onClick={(e) => { e.stopPropagation(); runAction('reset', provName, p.lastUsed); }}
127
+ title={t('quickResetTitle')}
128
+ style={{ padding: '2px 6px', fontSize: '11px', borderRadius: '4px', border: '1px solid var(--dsw-alias-border-subtle)', background: 'transparent', cursor: 'pointer', color: 'inherit' }}
129
+ >{`🔄 ${t('quickReset')}`}</button>
130
+ <button
131
+ type="button"
132
+ disabled={busy || !p.lastUsed}
133
+ onClick={(e) => { e.stopPropagation(); runAction('pause', provName, p.lastUsed); }}
134
+ title={t('quickPauseTitle')}
135
+ style={{ padding: '2px 6px', fontSize: '11px', borderRadius: '4px', border: '1px solid var(--dsw-alias-border-subtle)', background: 'transparent', cursor: 'pointer', color: 'inherit' }}
136
+ >{`⏸️ ${t('quickPause')}`}</button>
137
+ </div>
138
+ </div>
139
+ );
140
+ })}
141
+ </div>
142
+ ) : null}
143
+ </div>
136
144
  );
137
145
  }