Originally created by: Maqbool61
Closes [#336]
Makes the three egress-velocity detection thresholds (burst, volume_bytes,
fanout) policy-configurable with strict raise-only semantics — tightening
applies automatically, loosening gates through the existing possession-factor
weaken path (TOTP if enrolled, else password), never silently.
egress/velocity.py — adds VelocityThresholds frozen dataclass (defaults
to the existing module constants, so all existing callers are unchanged);
EgressVelocityTracker.__init__ now accepts thresholds: VelocityThresholds | None;
removes the ponytail: comment; leaves _MAX_EVENTS_PER_ENTITY /
_MAX_TRACKED_ENTITIES untouched (memory-safety caps, not policy knobs).
engine/rules/destinations.py — ExternalDestinationRule.__init__ gains
velocity_thresholds: VelocityThresholds | None = None and passes it through
to the tracker. Pattern mirrors bulk_threshold in DestructiveCommandRule
(commands.py:762).
policy/checklist.py — PolicyDoc gains egress_velocity_thresholds:
VelocityThresholds | None = None, with_egress_velocity_thresholds() copy-
method, to_mapping() serialisation, and fail-closed from_mapping()
deserialisation. Mirrors the preferences field shape throughout.
policy/drift.py — adds _velocity_classify(before, after) and
apply_egress_velocity_change(...) as a structural sibling of
apply_preferences_change. Classifies against the current effective stored
thresholds (before), not the built-in defaults — so a walk-back toward the
default after a prior gate-approved loosening is correctly flagged as a weaken.
proxy/executor.py — _default_objective_rules() accepts optional
velocity_thresholds and forwards it to ExternalDestinationRule.
proxy/serve.py — after REPO_ROOT is set, reads load_policy() and
rebuilds DEFAULT_OBJECTIVE with the stored thresholds so the long-lived
proxy singleton actually uses them at runtime.
tests/unit/test_drift_egress_velocity_gate.py (11 tests) — mirrorstest_drift_preferences_gate.py: loosening gates, tightening is free,tests/integration/test_egress_velocity_policy_wiring.py (11 tests) —load_policy → rule → correct trip.All pre-existing tests pass. ruff check, ruff format, lint-imports
(3 kept, 0 broken), and markdown link checks all clean.
Originally posted by: Maqbool61
Re: remaining CI failure — test_installed_plugin_is_discovered_and_fires_despite_pip_target
Confirmed pre-existing, unrelated to this PR. I reproduced the failure on a completely fresh clone with none of our changes:
bash
Fresh clone — separate directory, no our files
git clone https://github.com/fu351/Doberman-Core.git Temp/Doberman-Core
cd Temp/Doberman-Core
pip install -e ".[dev]"
python -m pytest tests/integration/test_real_plugin_install_discovery.py -v
→ FAILED: ModuleNotFoundError: No module named 'doberman'
Same error, same traceback, on main with zero changes from this PR. The test spawns an isolated subprocess venv and expects doberman to be discoverable via entry-point — this requires a non-editable wheel install inside that subprocess env, which doesn't happen on this machine. It passes in CI because the CI environment installs doberman differently.
All 3,067 other tests pass. The 33 tests added by this PR all pass. Ready for review.
Ticket changed by: fu351
Originally posted by: fu351
Merged, thanks @Maqbool61!
_velocity_classifyis the heart of this and you built it right: comparing against the current effective thresholds so a walk-back toward the default reads as the weaken it is, and treating a dropped key as a weaken closed the bypass I would have looked for first. The gate has no production caller yet, which matches the plan we agreed on, so I've opened [#457] for the CLI wiring and I'd love for you to take it since the gate is yours.Related
Tickets:
#457