PR18.6: enable trigger poller via config + env - #4378
Conversation
Wire the scheduled-trigger poller so operators can opt in. Adds a [trigger_poller] config section and IRONCLAW_TRIGGER_POLLER_ENABLED / _INTERVAL_SECS env overrides (env > config > default-off). Enforces the V1 max_concurrent_fires_per_trigger == 1 invariant. Off by default. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Fix doc comments in TriggerPollerConfigSection to match the actual composition defaults (poll_interval_secs 30, fires_per_tick 32) so operators leaving fields unset get the rate they expect. - Drop unused Serialize derive and the per-field #[serde(default, skip_serializing_if = "Option::is_none")] attrs on TriggerPollerConfigSection. The struct is Deserialize-only, so the serialize guards were dead; the field-level default is redundant because Option<T> already deserializes as None when absent. Matches every other *Section struct in the file. - Reject [trigger_poller].fires_per_tick = 0 at config-parse time so operators see the error during boot config validation rather than later at spawn_trigger_poller. Symmetric with the existing poll_interval_secs > 0 and max_concurrent_fires_per_trigger == 1 guards. - Preserve the underlying parse error in the IRONCLAW_TRIGGER_POLLER_INTERVAL_SECS error message so values like "10s" or "1.5" tell the operator why they failed instead of just "must be a positive integer". - Wrap test env-var mutation in a panic-safe RAII guard (restore on Drop) and serialize env-touching tests via a static LazyLock<Mutex>. Without this the new env tests race each other under the default parallel test harness; the previous manual snapshot/restore also leaked env state on assertion panic. - Extract trigger_poller_settings + the env-mutation test harness into crates/ironclaw_reborn_cli/src/runtime/trigger_poller.rs. runtime.rs becomes runtime/mod.rs, dropping from 1231 to 883 lines (back below the 1000-line soft norm in .claude/rules/architecture.md). Keeps optional_nonempty_env in mod.rs as pub(super) since the child uses it. - Clarify .env.example: IRONCLAW_TRIGGER_POLLER_ENABLED accepts 1/true to enable and 0/false to disable; any other value is a fatal startup error. - Add four tests: fires_per_tick=0, IRONCLAW_TRIGGER_POLLER_ENABLED=yes (invalid), IRONCLAW_TRIGGER_POLLER_INTERVAL_SECS env override wins over config, and IRONCLAW_TRIGGER_POLLER_INTERVAL_SECS="10s" preserves the parse error context. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
henrypark133
left a comment
There was a problem hiding this comment.
Code Review (8 reviewers, sonnet) — head baa7a1b
Intent: Enable trigger poller via Reborn TOML config + env-var overrides. Default off in shipped profiles. Boot-time invariant validation. Module split to keep runtime/mod.rs under 1000-line soft norm.
Stats: 5 findings (from 7 raw, 5 after dedup) across 3 files. Reviewers run: security, bugs, performance, tests, conventions, local-patterns, maintainability, pattern-refactor. Reviewers failed: none. Body-only: 0. bugs/performance/conventions/maintainability/pattern-refactor all clean.
Event: COMMENT — no Critical/High ≥75. Clean structurally. Findings are robustness + test-coverage gaps.
Findings
Tests
-
Medium
build_runtime_inputnever asserts trigger_poller wiring reaches RebornRuntimeInput (crates/ironclaw_reborn_cli/src/runtime/mod.rs:260-267, confidence 85) — anchor::263- Per
.claude/rules/testing.mdTest-Through-the-Caller:trigger_poller_settings()is wired through.with_trigger_poller_settings()insidebuild_runtime_input_with_options(), but no caller-level test asserts[trigger_poller] enabled=truein config.toml propagates toruntime_input.trigger_poller.enabled. Silent drop in the wiring would pass all existing tests. - Fix: add
build_runtime_input_maps_trigger_poller_enabled_configwriting config.toml + asserting propagation.
- Per
-
Low Env
ENABLED=falseoverriding config-enabled poller untested (kill-switch path) (crates/ironclaw_reborn_cli/src/runtime/trigger_poller.rs:89-96, confidence 65) — anchor::90- Existing tests cover env=true overriding config=false but not reverse. Deploy-time kill-switch path uncovered. The
"0" | "false" =>arm at lines 89-91 untested.
- Existing tests cover env=true overriding config=false but not reverse. Deploy-time kill-switch path uncovered. The
Security
-
Medium
fires_per_tick+ jitter fields accept unbounded u32/u64 (crates/ironclaw_reborn_config/src/config_file.rs:264-281, confidence 65) — anchor:crates/ironclaw_reborn_cli/src/runtime/trigger_poller.rs:64- Typo footgun: 86400000 (ms instead of secs) suspends poller ~2.7 years;
u64::MAX→ effectively forever.fires_per_tick = u32::MAXattempts ~4B dispatches/tick. Boot validation only rejects 0/!=1. - Fix: cap
fires_per_tick <= 1000,jitter_max_secs <= 3600intrigger_poller_settings()withanyhow::bail!.
- Typo footgun: 86400000 (ms instead of secs) suspends poller ~2.7 years;
-
Low Raw env-var value echoed into startup error messages (
crates/ironclaw_reborn_cli/src/runtime/trigger_poller.rs:100-107, confidence 55) — anchor::102{raw:?}/{other:?}echoes full env-var content. Operator copy-paste error (credential into wrong env slot) would land secret in startup error logs / crash reporters. Hardening only.- Fix: truncate echoed value to ~64 chars before format.
Local patterns
- Nit Parse test uses
max_concurrent=3silently (crates/ironclaw_reborn_config/src/config_file.rs:1284-1306, confidence 65) — anchor::1290trigger_poller_full_section_parsesuses 3 to confirm config layer is pure deserializer; V1 invariant (must be 1) is CLI-layer. No inline comment explains intent. Reader could copy 3 into operator config and hit fatal boot error.- Fix: inline comment noting deliberate non-prod-valid choice, or change to 1 + add separate test for non-1 parse acceptance.
Other categories
- bugs ✅ clean — drop order on EnvGuards verified correct, env_lock + early-bail layers analyzed
- performance ✅ clean — startup cold-path only; test-env-race acknowledged inline in code
- conventions ✅ clean —
pub(super)widening ofoptional_nonempty_envis intra-module idiomatic - maintainability ✅ clean — two
*_settings()functions don't yet meet threshold for shared "layered config merge" helper - pattern-refactor ✅ empty — 6
if let Some(x) = ...field-apply repetitions are correct idiom for optional config
Mergeability
✅ Recommend merge once the Medium test gap (#1) is closed. Other findings are robustness improvements that can land in follow-ups.
- Add caller-level integration test build_runtime_input_maps_trigger_poller_enabled_config: writes a config.toml with [trigger_poller] enabled=true poll_interval_secs=42, drives build_runtime_input(), asserts the values reach the returned RebornRuntimeInput. Closes the Test-Through-the-Caller gap flagged in review (.claude/rules/testing.md): the unit-level helper tests on trigger_poller_settings alone could not catch a silent drop in the with_trigger_poller_settings(...) wiring. Test snapshots and restores both IRONCLAW_TRIGGER_POLLER_* env vars via a panic-safe local guard so a parent-shell setting cannot mask the assertion. - Truncate raw env-var values to 64 chars (char-aware) before echoing them into IRONCLAW_TRIGGER_POLLER_ENABLED and IRONCLAW_TRIGGER_POLLER_INTERVAL_SECS error messages. Preserves the existing debuggability for short typos (the test asserts on "yes" and "10s" still hold) while bounding the worst case where an operator accidentally pastes a long string — for example a credential — into the env slot and the value lands verbatim in startup logs / crash reporters. - Add kill-switch test trigger_poller_settings_env_disabled_overrides_config_enabled: exercises the "0" | "false" => settings.enabled = false arm with config-enabled true, asserting the env disables. This was the inverse of the existing env-enables-when-config-disables test; both directions are now covered. - Add a 4-line comment above the max_concurrent_fires_per_trigger = 3 assertion in trigger_poller_full_section_parses explaining that this value is intentional at the parse layer (which is a pure deserializer) and that the V1 invariant (must equal 1) is enforced by trigger_poller_settings in runtime/trigger_poller.rs. An operator reading just the parse test will not mis-copy 3 into their config. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reject misconfigured values at boot rather than silently suspending the poller for hours or attempting billions of dispatches per tick. The reviewer-flagged footgun is the common typo class of writing milliseconds instead of seconds — e.g. 86_400_000 in poll_interval_secs would have parked the poller for ~2.7 years without any operator-visible signal. Caps introduced (all enforced in runtime/trigger_poller.rs at parse time, with descriptive error messages naming the field/env var, the allowed range, and the offending value): - poll_interval_secs: 1..=3600 (1h ceiling) - IRONCLAW_TRIGGER_POLLER_INTERVAL_SECS: 1..=3600 - fires_per_tick: 1..=1000 (default is 32) - startup_jitter_max_secs: 0..=3600 - tick_jitter_max_secs: 0..=3600 Cap rationale: - 1h on the time-shaped knobs is the largest value that still feels like a poll interval / jitter rather than a multi-hour scheduled pause. Common millisecond typos (60_000, 86_400_000) all blow past 3600 and now fail loudly. - 1000 on fires_per_tick leaves ~30x headroom over the 32 default for future high-throughput deployments while rejecting accidents like u32::MAX. Five new tests cover the upper-bound failure path for each knob including the env override. The two doc strings on the config-file fields and the .env.example line now document the allowed ranges so operators see the constraint in the config surface, not just on boot failure. The existing > 0 / == 1 guards are preserved; the new ranges compose with them (1..=N rather than > 0). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
henrypark133
left a comment
There was a problem hiding this comment.
Code Review Round 2 (8 reviewers, sonnet) — head 7bdeedc
Intent: R2 follow-up — cap trigger poller knobs at sane upper bounds + address R1 review feedback.
Round-1 findings — all RESOLVED ✅:
- ✅ R1 Med (conf 85) caller-level test →
build_runtime_input_maps_trigger_poller_enabled_configadded in mod.rs - ✅ R1 Med (conf 65) unbounded knobs →
MAX_POLL_INTERVAL_SECS=3600/MAX_JITTER_SECS=3600/MAX_FIRES_PER_TICK=1000caps +max_concurrent != 1strict-eq invariant - ✅ R1 Low (conf 65) kill-switch test →
env_disabled_overrides_config_enabledadded - ✅ R1 Low (conf 55) raw env-var echo →
truncate_env_value_for_display(64-char cap) used in all error paths - ✅ R1 Nit (conf 65)
max_concurrent=3parse test → inline comment explaining parse-layer vs CLI invariant added
Stats: 7 findings (from 10 raw, 7 after dedup) across 3 files. Reviewers run: security, bugs, performance, tests, conventions, local-patterns, maintainability, pattern-refactor. Reviewers failed: none. Body-only: 0. security/conventions/pattern-refactor all clean. No deferred-from-R1 items remaining.
Event: COMMENT — no Critical/High ≥75.
Findings
Cross-module test isolation (NEW, highest priority)
- Medium Caller-level test in mod.rs races with
trigger_poller::tests::TRIGGER_ENV_LOCK(crates/ironclaw_reborn_cli/src/runtime/mod.rs:794-854, confidence 82) — anchor::820- New caller-level test mutates
IRONCLAW_TRIGGER_POLLER_ENABLED+INTERVAL_SECSvia inlineEnvGuardwithout acquiringTRIGGER_ENV_LOCK(module-private totrigger_poller::tests).cargo testruns both modules in same binary in parallel → race against any of 11+ env-touching trigger_poller tests → non-deterministic CI. - Also flagged by maintainability/Low: same root cause —
EnvGuardduplicated inline at mod.rs:798-819 instead of sharing with canonicalEnvGuardin trigger_poller.rs. - Fix: expose
lock_trigger_env()+EnvGuardaspub(super)fromtrigger_poller::tests, OR extract both into a#[cfg(test)] mod test_envatruntime/level. Removes both race AND duplicated inlineEnvGuard.
- New caller-level test mutates
Bugs
- Low Invalid-value error shows lowercased value, not what operator typed (
crates/ironclaw_reborn_cli/src/runtime/trigger_poller.rs:132-136, confidence 70) — anchor::133raw.to_ascii_lowercase()→other.truncate_env_value_for_display(other)→ error shows lowercased form. Operator withENABLED=YESseesgot "yes", obscuring what to grep in secrets config.- Fix: pass
&raw(original), notother.
Tests (boundary coverage)
-
Low At-cap boundary acceptance untested for poll_interval=3600 / fires_per_tick=1000 / jitter=3600 (
crates/ironclaw_reborn_cli/src/runtime/trigger_poller.rs:73-102, confidence 75) — anchor::74- Strict
>validation means exact cap MUST be accepted. Tests cover above-cap rejection + below-cap acceptance, never exact cap. Off-by-one>→>=regression silently invalidates documented maximum. - Fix: add 3 at-cap acceptance tests.
- Strict
-
Low
max_concurrent_fires_per_trigger=0untested as rejection (crates/ironclaw_reborn_cli/src/runtime/trigger_poller.rs:93-102, confidence 75) — anchor::95!= 1guard rejects 0 and 2+. Only 2 tested. 0 has distinct semantic meaning (unlimited concurrency in some systems) — explicit-rejection confirmation valuable.
-
Low Numeric
ENABLEDaliases'1'and'0'untested (crates/ironclaw_reborn_cli/src/runtime/trigger_poller.rs:128-138, confidence 75) — anchor::130- Match accepts
'1' | 'true'/'0' | 'false'. All env tests usetrue/false..env.exampledocuments1/0as primary examples. Alias removal regression silently undetected.
- Match accepts
-
Low
truncate_env_value_for_displaynot tested with >64-char input (crates/ironclaw_reborn_cli/src/runtime/trigger_poller.rs:28-37, confidence 50) — anchor::28- Helper hardens against credential-sized values. Truncation
…suffix + raw-value exclusion never asserted.
- Helper hardens against credential-sized values. Truncation
Local patterns
- Nit Doc-comment cites internal module path
runtime/trigger_poller.rs(crates/ironclaw_reborn_config/src/config_file.rs:267-269, confidence 55) — anchor::268- Sibling section docs (WebuiSection, BudgetSection, RunnerSection) describe enforcement in terms of behavior, not internal file paths. Path reference rots silently on file move/rename.
- Fix: replace with behavior-describing prose matching sibling style.
Other categories
- security ✅ clean — all R1 caps in place, truncate helper working as documented
- conventions ✅ clean —
pub(super)widening + module split justified by architecture.md soft norm - pattern-refactor ✅ empty — sequential
if let Some(...)field-apply correct for distinct-typed config fields
Mergeability
✅ All R1 findings RESOLVED. No High/Critical remain.
Recommend before merge: close the cross-module test race (#1, Med conf 82) — same-binary tests with parallel cargo run will hit it. The lowercased-echo bug (#2) is one-line. Test gaps #3-#6 are valuable coverage but can land in follow-ups. #7 is a nit.
Review feedback (7 comments):
- Extract `runtime/test_env.rs` (cfg-test only) housing `TRIGGER_ENV_LOCK`,
`lock_trigger_env()`, and `EnvGuard`. The previous round's caller-level
test in `runtime::tests` mutated the same `IRONCLAW_TRIGGER_POLLER_*`
env vars as the unit tests in `trigger_poller::tests` but did NOT
acquire the lock that the latter owns — both modules compile into the
same test binary and cargo runs them in parallel, so the caller-level
test could race with any of the 11 unit env tests. The shared module
closes the race AND eliminates the duplicated inline EnvGuard.
- Fix invalid-ENABLED error message to echo the operator's original
value (case preserved), not the lowercased match key. Before:
`IRONCLAW_TRIGGER_POLLER_ENABLED=YES` showed `got "yes"`; the operator
couldn't grep their config for `yes` because they typed `YES`. New
test `trigger_poller_settings_env_enabled_invalid_value_preserves_case`
pins the contract.
- Add three at-cap boundary acceptance tests for `poll_interval_secs=3600`,
`fires_per_tick=1000`, `jitter=3600`. The bounds guard uses `>` not
`>=`, so the exact cap value MUST succeed — without these tests an
off-by-one regression would silently invalidate the documented maximum.
- Add `trigger_poller_settings_max_concurrent_fires_zero_is_error`
covering the 0 path (existing test covered only 2). 0 has distinct
semantic meaning in some systems (unlimited concurrency); the V1
guard's explicit rejection is now pinned.
- Add `env_enabled_numeric_one_enables` / `env_enabled_numeric_zero_disables`.
`.env.example` documents `1`/`0` as primary forms but no test exercised
them; a regression dropping the numeric arms would have shipped.
- Add `env_enabled_long_value_is_truncated` asserting the truncation
ellipsis appears AND the full 80-char raw value does NOT appear in the
error. Locks in the hardening contract for the `>64`-char path.
- Reword `TriggerPollerConfigSection` field doc comments to describe
enforcement by behavior ("enforced at boot by the CLI settings layer"),
not by file path. Matches sibling sections (`WebuiSection`,
`BudgetSection`); won't rot if the file ever moves.
CI fix:
- `reborn_cli_binary_crate_stays_separate_from_v1_root` in
`crates/ironclaw_architecture/tests/reborn_dependency_boundaries.rs`
hard-coded `std::fs::read_to_string("crates/.../src/runtime.rs")`, which
was correct when runtime was a single file. The round-2 fixes split it
into `runtime/mod.rs` + sibling helper files, so the test now panics
on missing path on CI. Switched the check to iterate every `.rs` file
under the `runtime/` directory and concatenate them before applying
the forbidden-import and `build_reborn_runtime` checks. This also
fixes a latent gap: forbidden imports in a child file would have
silently slipped past the old check that only scanned the entry
point. All 23 boundary tests still pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
serrrfirat
left a comment
There was a problem hiding this comment.
Code Review (5 reviewers) — head ddc540f
Intent: Expose Reborn trigger-poller config/env controls while keeping the shipped default off and enforcing startup invariants.
Stats: 4 findings after aggregation across 4 files. Reviewers run: security, bugs, performance/concurrency, tests, conventions. Reviewers failed: none. Security and performance/concurrency returned clean.
Event: COMMENT — no Critical/High findings.
Findings
Bugs
- Medium Blank trigger-poller env vars are silently ignored (confidence 85) —
crates/ironclaw_reborn_cli/src/runtime/trigger_poller.rs:128optional_nonempty_env()trims and filters empty values, so explicitly settingIRONCLAW_TRIGGER_POLLER_ENABLED=orIRONCLAW_TRIGGER_POLLER_INTERVAL_SECS=behaves as if the env var is absent. These env vars are documented as strict overrides where invalid values are fatal, and ignoring blank values can also defeat env precedence over a config-enabled poller.- Fix: read these strict vars as present-or-absent with
std::env::var, trim only for parsing, and reject empty/whitespace values through the same invalid-value path.
Tests
- Medium Missing caller-level coverage for trigger-poller env overrides (confidence 85) —
crates/ironclaw_reborn_cli/src/runtime/mod.rs:265- Helper tests cover env/config precedence and the caller-level test covers config-only propagation, but no caller-level test proves
IRONCLAW_TRIGGER_POLLER_*env overrides survivebuild_runtime_input_with_options()and reachRebornRuntimeInput. Per the repo's test-through-the-caller rule, this wiring gates runtime worker startup and should be covered at the call site. - Fix: add
build_runtime_input_env_trigger_poller_overrides_configinruntime::tests, holdinglock_trigger_env(), setting both env vars, callingbuild_runtime_input, and assertingenabled=trueplus the env interval.
- Helper tests cover env/config precedence and the caller-level test covers config-only propagation, but no caller-level test proves
Conventions
-
Low Trigger-poller config docs describe a nonexistent CLI precedence layer (confidence 85) —
crates/ironclaw_reborn_config/src/config_file.rs:259- The public doc comment says CLI flags override env vars, but this PR implements only env > config > compiled defaults and adds no trigger-poller CLI flags. That makes the exported config contract stale.
- Fix: remove the CLI-flags sentence unless this PR wires an actual higher-precedence CLI layer.
-
Low Runtime boundary guard only scans one directory level (confidence 65) —
crates/ironclaw_architecture/tests/reborn_dependency_boundaries.rs:243- The new boundary-test comment says it scans every
.rsfile under the runtime module, butstd::fs::read_dir(&runtime_dir)only visits immediate children. A future nested runtime module could bypass the forbidden-import string guard. - Fix: make the scan recursive, or assert that
runtime/has no nested directories so the guardrail matches its documented coverage.
- The new boundary-test comment says it scans every
Validation
- Passed:
cargo test -p ironclaw_architecture reborn_cli_binary_crate_stays_separate_from_v1_root - Attempted:
cargo test -p ironclaw_reborn_cli -p ironclaw_reborn_config, but the local machine ran out of disk space while compiling dependencies (No space left on device), before test execution completed.
serrrfirat
left a comment
There was a problem hiding this comment.
Thermo-Nuclear Code Quality Review — head ddc540f
I focused this pass on structural maintainability: file-size pressure, abstraction quality, spaghetti growth, and whether the new trigger-poller config path fits the existing Reborn config/runtime boundaries.
Result: 1 high-signal maintainability finding. I am not repeating the regular review's lower-level test/docs/boundary comments here.
Finding
- Medium The strict trigger-poller env parser is built on the wrong abstraction —
crates/ironclaw_reborn_cli/src/runtime/mod.rs:433/crates/ironclaw_reborn_cli/src/runtime/trigger_poller.rs:128
optional_nonempty_env() is an OAuth-style helper: trim, collapse blank to absent, and keep optional configuration easy. Exporting it with pub(super) so trigger_poller_settings() can consume strict operator controls makes the new module inherit the wrong model. For these env vars, “present but blank” is not “not configured”; it is an invalid override that should fail loudly. The helper hides that distinction, so the code now has to rely on comments and tests to remember which env vars are optional and which are strict.
Can we keep optional_nonempty_env private to the optional-config/OAuth path and give trigger poller its own small strict-env reader instead? A clean shape would be something like read_trigger_env(name) -> anyhow::Result<Option<String>> that preserves presence, trims only for parsing, and rejects empty/whitespace in one place. Then the enabled and interval branches express the real invariant directly, instead of sharing a helper whose name and behavior encode the opposite contract.
Structure Check
runtime/mod.rsstays below 1k lines: 876 lines at base, 925 at head.- New
runtime/trigger_poller.rsis 612 lines, under the repo's new-file target; most of the size is focused tests, so I do not see a file-size blocker. - The direct default → section → env merge shape matches nearby
runner_settings; a heavier reusable config resolver would be over-abstracting for this PR.
abbyshekit
left a comment
There was a problem hiding this comment.
Code review — PR18.6: enable trigger poller via config + env
Multi-agent review (security · bugs · performance · tests · conventions) at ddc540f7. Diff-only mode.
Independent review of an open PR.
3 findings — 3 Low. Posted as a comment (advisory). Confidence ≥ 50, deduplicated across reviewers.
| Sev | Conf | Reviewer | Location | Finding |
|---|---|---|---|---|
| Low | 70% | conventions | reborn_dependency_boundaries.rs:237 |
Boundary scan over runtime/ is non-recursive, contradicting its own comment and diverging from the four recursive walkers in this same file |
| Low | 55% | tests | trigger_poller.rs:128 |
Empty/whitespace env-var branch is untested (could silently regress into a fatal startup bail) |
| Low | 50% | tests | mod.rs:798 |
Env-override precedence has no caller-level (build_runtime_input) test; only the config-file path is exercised through the caller |
Generated by near-ai-code-review (5 parallel reviewer agents + intent analysis). Diff-only; confidence ≥ 50; ≤ 15 inline comments.
Two reviewers (serrrfirat, abbyshekit) opened 8 comments after round 3.
Three are straightforward and applied here; one was a contradicting pair
about blank env-var semantics that needed an explicit contract choice.
Straightforward fixes:
- runtime/mod.rs: two new caller-level tests through build_runtime_input
exercise the env path (not just config). build_runtime_input_env_enables_
trigger_poller_with_no_config_section asserts IRONCLAW_TRIGGER_POLLER_
ENABLED=true with no [trigger_poller] config reaches input.trigger_poller.
build_runtime_input_env_interval_overrides_config_interval asserts env
interval wins over config interval through the wired path. Closes the
Test-Through-the-Caller gap flagged by both reviewers on the env axis.
- config_file.rs: drop the "CLI flags override env vars" clause from the
TriggerPollerConfigSection doc comment. This PR ships no CLI flags;
the sentence promised behavior that does not exist. New text names the
IRONCLAW_TRIGGER_POLLER_* prefix instead.
- reborn_dependency_boundaries.rs: replace the flat std::fs::read_dir on
runtime/ with a recursive collect_runtime_rs helper mirroring the
is_dir-recurse pattern used by collect_forbidden_turns_identifier_uses,
collect_forbidden_string_uses, collect_forbidden_runtime_network_uses,
and collect_forbidden_uses elsewhere in the same file. The previous
flat scan contradicted its own comment ("every .rs file under runtime")
and would have missed a forbidden import added to a future
runtime/<sub>/x.rs.
Strict env contract (operator chose this over the falling-through
alternative):
- runtime/trigger_poller.rs: new strict_env_var(name) -> anyhow::Result
<Option<String>>. Unset → Ok(None). Set-but-empty or whitespace-only →
fatal. Set with content → Ok(Some(_)) (caller validates). Both
IRONCLAW_TRIGGER_POLLER_ENABLED and IRONCLAW_TRIGGER_POLLER_INTERVAL_
SECS now read through strict_env_var instead of the lenient
optional_nonempty_env. Operators get a clear boot failure for a blank
env slot (shell typo, half-set deployment template, credential
injector that failed to populate) rather than a silent fall-through
that drops their intended kill-switch or interval override.
- runtime/mod.rs: revert optional_nonempty_env to private (no longer
needed pub(super)). Updated its doc comment to call out that operator-
control knobs should NOT use it and to point at strict_env_var.
- Three new tests cover the strict contract:
trigger_poller_settings_env_enabled_empty_is_error,
trigger_poller_settings_env_enabled_whitespace_is_error,
trigger_poller_settings_env_interval_empty_is_error.
Each holds lock_trigger_env() and asserts the env-var name appears in
the bail message so future log scrapers / on-call docs stay accurate.
Test count: 62 cli (was 59, +3), 45 config, 23 architecture. Clippy
clean on the three touched crates.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
henrypark133
left a comment
There was a problem hiding this comment.
Code Review Round 4 (8 reviewers, sonnet) — head fef95a1
Intent: R4 follow-up — address R2 + R3 review feedback. Extract shared runtime/test_env.rs to fix cross-module race; bug fixes; boundary coverage.
Prior round findings — ALL RESOLVED ✅:
R2 (my last round):
- ✅ R2 Med (82) cross-module race → new
runtime/test_env.rsshared module; bothmod.rs::testsandtrigger_poller::testsuselock_trigger_env()+ sharedEnvGuard - ✅ R2 Low (70) lowercase echo → now passes
&raw(original case) - ✅ R2 Low (75) at-cap acceptance → 3
*_at_cap_is_acceptedtests added - ✅ R2 Low (75) max_concurrent=0 → test added
- ✅ R2 Low (75) numeric ENABLED 1/0 → 2 tests added
- ✅ R2 Low (50) truncation >64-char → test added (80 x's, assert ellipsis + raw exclusion)
- ✅ R2 Nit (55) doc-comment internal path → reworded
R3 (non-me, head ddc540f):
- ✅ R3 Low (70) conventions non-recursive boundary scan →
collect_runtime_rsnow recursive - ✅ R3 Low (55) tests empty/whitespace env branch → 3 tests added (empty + whitespace + interval-empty)
- ✅ R3 Low (50) tests env-override caller-level → 2 new caller tests in mod.rs
Stats: 5 findings (from 5 raw, 5 after dedup) across 4 files. Reviewers run: security, bugs, performance, tests, conventions, local-patterns, maintainability, pattern-refactor. Reviewers failed: none. Body-only: 0. security/performance/conventions/maintainability/pattern-refactor all clean.
Event: COMMENT — no Critical/High ≥75. Findings are test-coverage gaps + style nits.
Findings
Bugs / Tests (Medium — close before merge)
-
Medium Three at-cap tests reach env layer without acquiring TRIGGER_ENV_LOCK (
crates/ironclaw_reborn_cli/src/runtime/trigger_poller.rs:529-570, confidence 80) — anchor::529*_at_cap_is_acceptedtests pass config validation and proceed into env layer without lock or env-clear. Concurrent test settingINTERVAL_SECS=45→ at-cap test reads 45 instead of 3600 → flaky failure. Or ENABLED=yes → expect() panics.- Fix: add
let _lock = lock_trigger_env(); _e/_i = EnvGuard::clear(...)at top of each.
-
Medium
build_runtime_inputinvalid-env error propagation untested at caller boundary (crates/ironclaw_reborn_cli/src/runtime/mod.rs:262-265, confidence 75) — anchor::262trigger_poller_settings()is?-propagated inbuild_runtime_input_with_options. Per.claude/rules/testing.mdTest-Through-the-Caller, error path needs caller-level coverage. Existing caller tests cover happy-path + override-wins; none drivesENABLED=yesthrough build_runtime_input and asserts Err propagation.
Local patterns
-
Low
collect_runtime_rsnested inside test body; sibling walkers are module-level (crates/ironclaw_architecture/tests/reborn_dependency_boundaries.rs:243-267, confidence 75) — anchor::243- Only nested-fn pattern in 2500-line file. Sibling walkers (
collect_forbidden_*,extract_pub_use_block) all at module level. Hoist to match pattern.
- Only nested-fn pattern in 2500-line file. Sibling walkers (
-
Nit Doc-comment on
optional_nonempty_envcites private rust-path that won't rustdoc-link (crates/ironclaw_reborn_cli/src/runtime/mod.rs:433-445, confidence 55) — anchor::438- Cites
runtime::trigger_poller::strict_env_var— also private. Path neither rustdoc-linkable nor externally navigable. Replace with prose or drop.
- Cites
-
Nit Redundant
#![cfg(test)]inner attribute in test_env.rs (crates/ironclaw_reborn_cli/src/runtime/test_env.rs:1-8, confidence 50) — anchor::1mod.rsalready gates with#[cfg(test)] mod test_env;. Inner attr is no-op + confusing about authoritative gate. Drop the inner attribute.
Other categories
- security ✅ clean — all R2 hardening preserved (truncate helper, original-case raw, caps)
- performance ✅ clean — config-only tests that skip lock all bail before env reads
- conventions ✅ clean — module split + visibility correct; no string fields means trigger_poller intentionally skipped in validate()'s secrets_guard sweep
- maintainability ✅ clean
- pattern-refactor ✅ empty
Mergeability
✅ All R2 + R3 findings RESOLVED. CI mergeable_state=clean.
Recommend before merge: close the 2 Mediums (#1 lock-missing in at-cap tests, #2 caller-level error-propagation test). Both are low-effort. The 3 Lows/Nits = follow-up.
Five low-friction items flagged by henrypark133 after round 4. - trigger_poller.rs: the three `*_at_cap_is_accepted` tests (poll_interval=3600, fires_per_tick=1000, jitter=3600) reached the env-var override layer in trigger_poller_settings without holding lock_trigger_env() or clearing the IRONCLAW_TRIGGER_POLLER_* env vars. A sibling test setting `IRONCLAW_TRIGGER_POLLER_INTERVAL_SECS=45` concurrently would have made the at-cap interval assertion (expects 3600) read 45 and falsely fail; an `ENABLED=yes` would have flipped the assertion to a panic. Each at-cap test now opens with the standard `lock_trigger_env()` + double `EnvGuard::clear` prologue used by the other env-flowing tests. The `*_above_cap_is_error` tests are unaffected — their config-layer bail fires before the env layer is ever consulted, so no env mutation can interfere. - mod.rs: new caller-level test build_runtime_input_rejects_invalid_trigger_poller_enabled_env. Sets IRONCLAW_TRIGGER_POLLER_ENABLED=yes, calls build_runtime_input(...), asserts the Err propagates with the env-var name in the message. RebornRuntimeInput does not implement Debug, so `expect_err` isn't available; switched to a match panic with the same intent. Closes the caller-level gap for the error path. Together with the existing enable/override tests, build_runtime_input now has caller-tier regression coverage for happy-path, env-override-wins, AND invalid-env-bails. - mod.rs: reword the doc on optional_nonempty_env. Previous text cited `runtime::trigger_poller::strict_env_var` as a rustdoc link, but both functions are private — the path doesn't link and would rot if a file moves. New text describes the contrast in prose: "a strict-presence variant in the `trigger_poller` submodule". - reborn_dependency_boundaries.rs: hoist `collect_runtime_rs` from a nested fn inside the test body to a module-level fn alongside the sibling `collect_forbidden_*` walkers. Matches the convention used by every other walker in this 2500-line file and lets a future boundary check reuse the helper without duplicating it. - test_env.rs: drop the redundant `#![cfg(test)]` inner attribute. runtime/mod.rs already declares `#[cfg(test)] mod test_env;`, so the inner gate was a no-op. No other test helper in the crate uses this double-gating. Test count: 63 cli (was 62, +1), 45 config, 23 architecture. Clippy clean on the three touched crates. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* PR18.6: enable trigger poller via config + env
Wire the scheduled-trigger poller so operators can opt in. Adds a
[trigger_poller] config section and IRONCLAW_TRIGGER_POLLER_ENABLED /
_INTERVAL_SECS env overrides (env > config > default-off). Enforces the
V1 max_concurrent_fires_per_trigger == 1 invariant. Off by default.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* PR18.6: address review feedback on trigger poller config
- Fix doc comments in TriggerPollerConfigSection to match the actual
composition defaults (poll_interval_secs 30, fires_per_tick 32) so
operators leaving fields unset get the rate they expect.
- Drop unused Serialize derive and the per-field
#[serde(default, skip_serializing_if = "Option::is_none")] attrs on
TriggerPollerConfigSection. The struct is Deserialize-only, so the
serialize guards were dead; the field-level default is redundant
because Option<T> already deserializes as None when absent. Matches
every other *Section struct in the file.
- Reject [trigger_poller].fires_per_tick = 0 at config-parse time so
operators see the error during boot config validation rather than
later at spawn_trigger_poller. Symmetric with the existing
poll_interval_secs > 0 and max_concurrent_fires_per_trigger == 1
guards.
- Preserve the underlying parse error in the
IRONCLAW_TRIGGER_POLLER_INTERVAL_SECS error message so values like
"10s" or "1.5" tell the operator why they failed instead of just
"must be a positive integer".
- Wrap test env-var mutation in a panic-safe RAII guard (restore on
Drop) and serialize env-touching tests via a static LazyLock<Mutex>.
Without this the new env tests race each other under the default
parallel test harness; the previous manual snapshot/restore also
leaked env state on assertion panic.
- Extract trigger_poller_settings + the env-mutation test harness into
crates/ironclaw_reborn_cli/src/runtime/trigger_poller.rs. runtime.rs
becomes runtime/mod.rs, dropping from 1231 to 883 lines (back below
the 1000-line soft norm in .claude/rules/architecture.md). Keeps
optional_nonempty_env in mod.rs as pub(super) since the child uses
it.
- Clarify .env.example: IRONCLAW_TRIGGER_POLLER_ENABLED accepts 1/true
to enable and 0/false to disable; any other value is a fatal
startup error.
- Add four tests: fires_per_tick=0, IRONCLAW_TRIGGER_POLLER_ENABLED=yes
(invalid), IRONCLAW_TRIGGER_POLLER_INTERVAL_SECS env override wins
over config, and IRONCLAW_TRIGGER_POLLER_INTERVAL_SECS="10s"
preserves the parse error context.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* PR18.6: address PR review feedback (round 2)
- Add caller-level integration test build_runtime_input_maps_trigger_poller_enabled_config:
writes a config.toml with [trigger_poller] enabled=true poll_interval_secs=42,
drives build_runtime_input(), asserts the values reach the returned
RebornRuntimeInput. Closes the Test-Through-the-Caller gap flagged in review
(.claude/rules/testing.md): the unit-level helper tests on trigger_poller_settings
alone could not catch a silent drop in the with_trigger_poller_settings(...)
wiring. Test snapshots and restores both IRONCLAW_TRIGGER_POLLER_* env vars via
a panic-safe local guard so a parent-shell setting cannot mask the assertion.
- Truncate raw env-var values to 64 chars (char-aware) before echoing them into
IRONCLAW_TRIGGER_POLLER_ENABLED and IRONCLAW_TRIGGER_POLLER_INTERVAL_SECS error
messages. Preserves the existing debuggability for short typos (the test asserts
on "yes" and "10s" still hold) while bounding the worst case where an operator
accidentally pastes a long string — for example a credential — into the env slot
and the value lands verbatim in startup logs / crash reporters.
- Add kill-switch test trigger_poller_settings_env_disabled_overrides_config_enabled:
exercises the "0" | "false" => settings.enabled = false arm with config-enabled
true, asserting the env disables. This was the inverse of the existing
env-enables-when-config-disables test; both directions are now covered.
- Add a 4-line comment above the max_concurrent_fires_per_trigger = 3 assertion
in trigger_poller_full_section_parses explaining that this value is intentional
at the parse layer (which is a pure deserializer) and that the V1 invariant
(must equal 1) is enforced by trigger_poller_settings in runtime/trigger_poller.rs.
An operator reading just the parse test will not mis-copy 3 into their config.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* PR18.6: cap trigger poller knobs at sane upper bounds
Reject misconfigured values at boot rather than silently suspending
the poller for hours or attempting billions of dispatches per tick.
The reviewer-flagged footgun is the common typo class of writing
milliseconds instead of seconds — e.g. 86_400_000 in
poll_interval_secs would have parked the poller for ~2.7 years
without any operator-visible signal.
Caps introduced (all enforced in runtime/trigger_poller.rs at parse
time, with descriptive error messages naming the field/env var, the
allowed range, and the offending value):
- poll_interval_secs: 1..=3600 (1h ceiling)
- IRONCLAW_TRIGGER_POLLER_INTERVAL_SECS: 1..=3600
- fires_per_tick: 1..=1000 (default is 32)
- startup_jitter_max_secs: 0..=3600
- tick_jitter_max_secs: 0..=3600
Cap rationale:
- 1h on the time-shaped knobs is the largest value that still
feels like a poll interval / jitter rather than a multi-hour
scheduled pause. Common millisecond typos (60_000, 86_400_000)
all blow past 3600 and now fail loudly.
- 1000 on fires_per_tick leaves ~30x headroom over the 32 default
for future high-throughput deployments while rejecting accidents
like u32::MAX.
Five new tests cover the upper-bound failure path for each knob
including the env override. The two doc strings on the config-file
fields and the .env.example line now document the allowed ranges
so operators see the constraint in the config surface, not just on
boot failure.
The existing > 0 / == 1 guards are preserved; the new ranges
compose with them (1..=N rather than > 0).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* PR18.6: address PR review feedback (round 3) + CI boundary fix
Review feedback (7 comments):
- Extract `runtime/test_env.rs` (cfg-test only) housing `TRIGGER_ENV_LOCK`,
`lock_trigger_env()`, and `EnvGuard`. The previous round's caller-level
test in `runtime::tests` mutated the same `IRONCLAW_TRIGGER_POLLER_*`
env vars as the unit tests in `trigger_poller::tests` but did NOT
acquire the lock that the latter owns — both modules compile into the
same test binary and cargo runs them in parallel, so the caller-level
test could race with any of the 11 unit env tests. The shared module
closes the race AND eliminates the duplicated inline EnvGuard.
- Fix invalid-ENABLED error message to echo the operator's original
value (case preserved), not the lowercased match key. Before:
`IRONCLAW_TRIGGER_POLLER_ENABLED=YES` showed `got "yes"`; the operator
couldn't grep their config for `yes` because they typed `YES`. New
test `trigger_poller_settings_env_enabled_invalid_value_preserves_case`
pins the contract.
- Add three at-cap boundary acceptance tests for `poll_interval_secs=3600`,
`fires_per_tick=1000`, `jitter=3600`. The bounds guard uses `>` not
`>=`, so the exact cap value MUST succeed — without these tests an
off-by-one regression would silently invalidate the documented maximum.
- Add `trigger_poller_settings_max_concurrent_fires_zero_is_error`
covering the 0 path (existing test covered only 2). 0 has distinct
semantic meaning in some systems (unlimited concurrency); the V1
guard's explicit rejection is now pinned.
- Add `env_enabled_numeric_one_enables` / `env_enabled_numeric_zero_disables`.
`.env.example` documents `1`/`0` as primary forms but no test exercised
them; a regression dropping the numeric arms would have shipped.
- Add `env_enabled_long_value_is_truncated` asserting the truncation
ellipsis appears AND the full 80-char raw value does NOT appear in the
error. Locks in the hardening contract for the `>64`-char path.
- Reword `TriggerPollerConfigSection` field doc comments to describe
enforcement by behavior ("enforced at boot by the CLI settings layer"),
not by file path. Matches sibling sections (`WebuiSection`,
`BudgetSection`); won't rot if the file ever moves.
CI fix:
- `reborn_cli_binary_crate_stays_separate_from_v1_root` in
`crates/ironclaw_architecture/tests/reborn_dependency_boundaries.rs`
hard-coded `std::fs::read_to_string("crates/.../src/runtime.rs")`, which
was correct when runtime was a single file. The round-2 fixes split it
into `runtime/mod.rs` + sibling helper files, so the test now panics
on missing path on CI. Switched the check to iterate every `.rs` file
under the `runtime/` directory and concatenate them before applying
the forbidden-import and `build_reborn_runtime` checks. This also
fixes a latent gap: forbidden imports in a child file would have
silently slipped past the old check that only scanned the entry
point. All 23 boundary tests still pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* PR18.6: address PR review feedback (round 4)
Two reviewers (serrrfirat, abbyshekit) opened 8 comments after round 3.
Three are straightforward and applied here; one was a contradicting pair
about blank env-var semantics that needed an explicit contract choice.
Straightforward fixes:
- runtime/mod.rs: two new caller-level tests through build_runtime_input
exercise the env path (not just config). build_runtime_input_env_enables_
trigger_poller_with_no_config_section asserts IRONCLAW_TRIGGER_POLLER_
ENABLED=true with no [trigger_poller] config reaches input.trigger_poller.
build_runtime_input_env_interval_overrides_config_interval asserts env
interval wins over config interval through the wired path. Closes the
Test-Through-the-Caller gap flagged by both reviewers on the env axis.
- config_file.rs: drop the "CLI flags override env vars" clause from the
TriggerPollerConfigSection doc comment. This PR ships no CLI flags;
the sentence promised behavior that does not exist. New text names the
IRONCLAW_TRIGGER_POLLER_* prefix instead.
- reborn_dependency_boundaries.rs: replace the flat std::fs::read_dir on
runtime/ with a recursive collect_runtime_rs helper mirroring the
is_dir-recurse pattern used by collect_forbidden_turns_identifier_uses,
collect_forbidden_string_uses, collect_forbidden_runtime_network_uses,
and collect_forbidden_uses elsewhere in the same file. The previous
flat scan contradicted its own comment ("every .rs file under runtime")
and would have missed a forbidden import added to a future
runtime/<sub>/x.rs.
Strict env contract (operator chose this over the falling-through
alternative):
- runtime/trigger_poller.rs: new strict_env_var(name) -> anyhow::Result
<Option<String>>. Unset → Ok(None). Set-but-empty or whitespace-only →
fatal. Set with content → Ok(Some(_)) (caller validates). Both
IRONCLAW_TRIGGER_POLLER_ENABLED and IRONCLAW_TRIGGER_POLLER_INTERVAL_
SECS now read through strict_env_var instead of the lenient
optional_nonempty_env. Operators get a clear boot failure for a blank
env slot (shell typo, half-set deployment template, credential
injector that failed to populate) rather than a silent fall-through
that drops their intended kill-switch or interval override.
- runtime/mod.rs: revert optional_nonempty_env to private (no longer
needed pub(super)). Updated its doc comment to call out that operator-
control knobs should NOT use it and to point at strict_env_var.
- Three new tests cover the strict contract:
trigger_poller_settings_env_enabled_empty_is_error,
trigger_poller_settings_env_enabled_whitespace_is_error,
trigger_poller_settings_env_interval_empty_is_error.
Each holds lock_trigger_env() and asserts the env-var name appears in
the bail message so future log scrapers / on-call docs stay accurate.
Test count: 62 cli (was 59, +3), 45 config, 23 architecture. Clippy
clean on the three touched crates.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* PR18.6: address PR review feedback (round 5)
Five low-friction items flagged by henrypark133 after round 4.
- trigger_poller.rs: the three `*_at_cap_is_accepted` tests
(poll_interval=3600, fires_per_tick=1000, jitter=3600) reached the
env-var override layer in trigger_poller_settings without holding
lock_trigger_env() or clearing the IRONCLAW_TRIGGER_POLLER_* env vars.
A sibling test setting `IRONCLAW_TRIGGER_POLLER_INTERVAL_SECS=45`
concurrently would have made the at-cap interval assertion (expects
3600) read 45 and falsely fail; an `ENABLED=yes` would have flipped
the assertion to a panic. Each at-cap test now opens with the
standard `lock_trigger_env()` + double `EnvGuard::clear` prologue
used by the other env-flowing tests. The `*_above_cap_is_error`
tests are unaffected — their config-layer bail fires before the env
layer is ever consulted, so no env mutation can interfere.
- mod.rs: new caller-level test
build_runtime_input_rejects_invalid_trigger_poller_enabled_env. Sets
IRONCLAW_TRIGGER_POLLER_ENABLED=yes, calls build_runtime_input(...),
asserts the Err propagates with the env-var name in the message.
RebornRuntimeInput does not implement Debug, so `expect_err` isn't
available; switched to a match panic with the same intent. Closes
the caller-level gap for the error path. Together with the existing
enable/override tests, build_runtime_input now has caller-tier
regression coverage for happy-path, env-override-wins, AND
invalid-env-bails.
- mod.rs: reword the doc on optional_nonempty_env. Previous text
cited `runtime::trigger_poller::strict_env_var` as a rustdoc link,
but both functions are private — the path doesn't link and would
rot if a file moves. New text describes the contrast in prose: "a
strict-presence variant in the `trigger_poller` submodule".
- reborn_dependency_boundaries.rs: hoist `collect_runtime_rs` from a
nested fn inside the test body to a module-level fn alongside the
sibling `collect_forbidden_*` walkers. Matches the convention used
by every other walker in this 2500-line file and lets a future
boundary check reuse the helper without duplicating it.
- test_env.rs: drop the redundant `#![cfg(test)]` inner attribute.
runtime/mod.rs already declares `#[cfg(test)] mod test_env;`, so
the inner gate was a no-op. No other test helper in the crate
uses this double-gating.
Test count: 63 cli (was 62, +1), 45 config, 23 architecture. Clippy
clean on the three touched crates.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Summary
Enable the trigger poller from
[trigger_poller]in the Reborn configfile plus two env-var overrides. PR 18 wired the lifecycle; PR 18.6
gives operators the surface to turn it on. Default stays off in
every shipped profile — no behavior change unless explicitly enabled.
What changed
ironclaw_reborn_config: new[trigger_poller]TOML section(
TriggerPollerConfigSection) with optional fields:enabled,poll_interval_secs,fires_per_tick,max_concurrent_fires_per_trigger,startup_jitter_max_secs,tick_jitter_max_secs.#[serde(deny_unknown_fields)]matches thesibling sections.
ironclaw_reborn_cli(runtime/trigger_poller.rs): newtrigger_poller_settings()helper merges three layers — compileddefault → config-file section → env vars. Precedence is env > config
IRONCLAW_TRIGGER_POLLER_ENABLED—1/trueenable,0/falsedisable (case-insensitive); any other value is afatal startup error.
IRONCLAW_TRIGGER_POLLER_INTERVAL_SECS—u64, must be > 0.spawn):
poll_interval_secs > 0,fires_per_tick > 0,max_concurrent_fires_per_trigger == 1. The third also re-checks atspawn in
TriggerPollerWorkerConfig::validate; the CLI guard iskept deliberately so misconfig fails during boot-config validation.
.env.example: documents the two env vars with their acceptedvalues and the fatal-on-bad-value behavior.
runtime.rs→runtime/mod.rs+ newruntime/trigger_poller.rsso the poller helper + its tests livein their own ~360-line file and
mod.rsstays below the 1000-linesoft norm in
.claude/rules/architecture.md.Test coverage
11 new unit tests in
runtime::trigger_poller::testsand 4 inconfig_file::tests:other fields at defaults
poll_interval_secs = 0,fires_per_tick = 0,max_concurrent_fires_per_trigger = 2, unknown TOML keyENABLED=trueoverrides config-false;INTERVAL_SECS=45overrides config interval;
INTERVAL_SECS=0errors;INTERVAL_SECS="10s"preserves the underlying parse error in themessage;
ENABLED=yeserrors with the offending valueEnv-mutating tests are serialized via a static
LazyLock<Mutex>anduse a panic-safe RAII
EnvGuardso a failed assertion cannot leakmutated env into sibling tests in the same process.
Out of scope
default-off; operator opt-in only.
18.10 (Automations panel).
(full-path integration) and PR 18.8 (Python e2e).
Test plan
cargo fmt --all --checkcargo clippy -p ironclaw_reborn_cli -p ironclaw_reborn_config --all-targets --all-featurescargo test -p ironclaw_reborn_cli -p ironclaw_reborn_config[trigger_poller] enabled = trueinto aReborn config, start the binary, confirm the poller spawn log
appears in stderr.
IRONCLAW_TRIGGER_POLLER_ENABLED=1and no config section.
🤖 Generated with Claude Code