Skip to content

Commit bb13580

Browse files
Merge pull request #24 from hyperi-io/chore/merge-to-release
chore: merge main into release
2 parents 2aaeb85 + 3b4260e commit bb13580

9 files changed

Lines changed: 1074 additions & 23 deletions

File tree

.cargo/audit.toml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Project: hyperi-rustlib
2+
# File: .cargo/audit.toml
3+
# Purpose: cargo-audit advisory ignore list for transitive deps
4+
# Language: TOML
5+
#
6+
# License: FSL-1.1-ALv2
7+
# Copyright: (c) 2026 HYPERI PTY LIMITED
8+
9+
# Ignored advisories — transitive deps we cannot fix.
10+
# Review on each major dependency update (especially sqlx, cel-interpreter).
11+
12+
[advisories]
13+
ignore = [
14+
# rsa timing side-channel (Marvin Attack), medium severity.
15+
# Transitive: sqlx -> sqlx-mysql -> rsa. We use PostgreSQL only.
16+
# No fixed version upstream. Revisit when sqlx drops default MySQL.
17+
"RUSTSEC-2023-0071",
18+
19+
# paste crate unmaintained (no security impact).
20+
# Transitive: cel-interpreter -> paste. We cannot control this.
21+
# Revisit when cel-interpreter updates or replaces paste.
22+
"RUSTSEC-2024-0436",
23+
]

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ coverage-html/
3737
coverage.lcov
3838
.cargo/
3939
!.cargo/config.toml
40+
!.cargo/audit.toml
4041

4142
# AI agent working files (not committed)
4243
docs/superpowers/

TODO.md

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,27 @@
88

99
## Current Tasks
1010

11-
### MemoryGuard: fix underflow in `release()`
12-
13-
`MemoryGuard::release()` uses `AtomicU64::fetch_sub` which wraps on underflow.
14-
Releasing more bytes than were added produces `u64::MAX - N` instead of saturating
15-
at zero. Found by dfe-loader resilience tests.
16-
17-
- [ ] Change `fetch_sub` to saturating subtraction (`fetch_update` with `saturating_sub`)
18-
- [ ] Add unit test for over-release scenario
19-
- [ ] Publish patch release
20-
21-
### CEL Expression Module — CI Fix Required `[IN PROGRESS]`
22-
23-
**Goal:** Publish v1.13.0 with `expression` feature to JFrog
24-
25-
Code is committed (`83713e6`), 425/426 tests pass. CI fails on pre-existing flaky test.
26-
27-
1. [x] Create `src/expression/` module (evaluator, profile, error)
28-
2. [x] Add `expression` feature flag with `cel-interpreter` dependency
29-
3. [x] 70 integration tests + 7 unit tests passing
30-
4. [x] Clippy pedantic clean (`#[must_use]`, `implicit_hasher`)
31-
5. [ ] Fix `test_instance_id_stable` flaky test (race condition on `~/.config/hyperi/instance_id`)
32-
6. [ ] CI green → Semantic Release → v1.13.0 → Publish to JFrog
11+
### Gap Analysis P2 — HTTP Client, Database URLs, Cache `[NEXT]`
12+
13+
- [ ] HTTP client module with retry middleware (reqwest + reqwest-middleware + reqwest-retry)
14+
- Wrap reqwest with exponential backoff, configurable timeouts
15+
- Auto-register config via `unmarshal_key_registered`
16+
- Metrics integration (request count, duration, errors)
17+
- [ ] Database URL builders (PostgreSQL, ClickHouse, Redis)
18+
- Build connection strings from env vars with standard prefixes
19+
- `SensitiveString` for password fields
20+
- [ ] Cache module with disk/memory backends
21+
- Consolidate secrets cache pattern into reusable module
22+
- TTL, stale-while-revalidate, size bounds
3323

3424
### Completed Recent
3525

26+
- [x] **Config registry** (v1.19.3-v1.19.5) — auto-registering reflectable config, `/config` admin endpoint, `SensitiveString`, heuristic redaction, change notification, `ConfigReloader` hook
27+
- [x] **CEL expression profile** (v1.19.2) — `matches()` blocked by default, `ProfileConfig` with per-category overrides, string literal false-positive prevention
28+
- [x] **Config cascade wiring** (v1.19.2) — expression, memory, version_check, scaling, grpc, secrets auto-read from figment cascade
29+
- [x] **MemoryGuard underflow fix** (v1.19.1) — `fetch_sub` replaced with `fetch_update` + `saturating_sub`
30+
- [x] **Test restructure** (v1.19.1) — `tests/integration/`, `tests/e2e/`, `tests/common/` per testing standard
31+
- [x] **hyperi-ci release-merge** — CLI command replaces per-project workflow files
3632
- [x] **Rust edition 2024** — migrated from 2021; `temp-env` replaces unsafe `set_var`/`remove_var` in tests across 6 files
3733
- [x] **async-trait removal** — public traits (`Sink`, `Transport`, `SecretProvider`) now use `fn ... -> impl Future + Send` (Rust 1.75+ native)
3834
- [x] **kafka_config module**`config_from_file`, 7 named profiles, `merge_with_overrides`; librdkafka settings loaded from config git dir (only cascade exception)
@@ -110,7 +106,9 @@ what config keys exist, their types, defaults, or descriptions.
110106
- [x] `/config` admin endpoint (opt-in via `enable_config_endpoint`) — returns redacted effective + defaults JSON
111107
- [x] Change notification (opt-in) — `registry::on_change(key, callback)` + `registry::update()`
112108
- Modules that need hot-reload subscribe; others keep `OnceLock` (init-once)
113-
- [ ] Wire `ConfigReloader` to call `registry::update()` on reload (connect the plumbing)
109+
- [x] `ConfigReloader.with_registry_update(key)` connects hot-reload to registry
110+
- [x] `SensitiveString` type — compile-time safe, `Serialize` always redacts
111+
- [x] 19 registry + 12 sensitive string tests covering all redaction guarantees
114112
- [ ] Migrate all dfe-* and hyperi-* apps to `unmarshal_key_registered` pattern
115113
- [ ] Align hyperi-pylib with same registry pattern
116114

src/cache/config.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Project: hyperi-rustlib
2+
// File: src/cache/config.rs
3+
// Purpose: Cache configuration
4+
// Language: Rust
5+
//
6+
// License: FSL-1.1-ALv2
7+
// Copyright: (c) 2026 HYPERI PTY LIMITED
8+
9+
//! Cache configuration with per-source TTL support.
10+
11+
use std::collections::HashMap;
12+
13+
use serde::{Deserialize, Serialize};
14+
15+
/// Configuration for the in-memory cache.
16+
#[derive(Debug, Clone, Serialize, Deserialize)]
17+
pub struct CacheConfig {
18+
/// Maximum number of entries. Default: 10,000.
19+
#[serde(default = "default_max_capacity")]
20+
pub max_capacity: u64,
21+
22+
/// Default TTL in seconds for entries without a source-specific TTL.
23+
/// Default: 3600 (1 hour).
24+
#[serde(default = "default_ttl_secs")]
25+
pub default_ttl_secs: u64,
26+
27+
/// Per-source TTL overrides in seconds.
28+
/// Keys are source names (e.g., "http", "db", "search").
29+
#[serde(default)]
30+
pub source_ttls: HashMap<String, u64>,
31+
}
32+
33+
fn default_max_capacity() -> u64 {
34+
10_000
35+
}
36+
37+
fn default_ttl_secs() -> u64 {
38+
3600
39+
}
40+
41+
impl Default for CacheConfig {
42+
fn default() -> Self {
43+
Self {
44+
max_capacity: default_max_capacity(),
45+
default_ttl_secs: default_ttl_secs(),
46+
source_ttls: HashMap::new(),
47+
}
48+
}
49+
}
50+
51+
impl CacheConfig {
52+
/// Load from the config cascade under the `cache` key.
53+
#[must_use]
54+
pub fn from_cascade() -> Self {
55+
#[cfg(feature = "config")]
56+
{
57+
if let Some(cfg) = crate::config::try_get()
58+
&& let Ok(cc) = cfg.unmarshal_key_registered::<Self>("cache")
59+
{
60+
return cc;
61+
}
62+
}
63+
Self::default()
64+
}
65+
}
66+
67+
#[cfg(test)]
68+
mod tests {
69+
use super::*;
70+
71+
#[test]
72+
fn defaults_are_sensible() {
73+
let config = CacheConfig::default();
74+
assert_eq!(config.max_capacity, 10_000);
75+
assert_eq!(config.default_ttl_secs, 3600);
76+
assert!(config.source_ttls.is_empty());
77+
}
78+
79+
#[test]
80+
fn deserialise_with_source_ttls() {
81+
let yaml = r"
82+
max_capacity: 5000
83+
default_ttl_secs: 1800
84+
source_ttls:
85+
http: 86400
86+
db: 900
87+
";
88+
let config: CacheConfig = serde_yaml_ng::from_str(yaml).unwrap();
89+
assert_eq!(config.max_capacity, 5000);
90+
assert_eq!(config.default_ttl_secs, 1800);
91+
assert_eq!(config.source_ttls["http"], 86400);
92+
assert_eq!(config.source_ttls["db"], 900);
93+
}
94+
}

0 commit comments

Comments
 (0)