fix: stop the run readout claiming a cap it is not applying - #14
Merged
Conversation
The readout printed "cap 2000" on every run, including runs with no collector. The events-per-second cap is only enforced where there is an emitter to throttle (orchestrator.py, the send loop), so a dry run or a file-only run is not limited at all. The screenshot in the README showed the result: "19098 events / sec" sitting directly beside "cap 2000". The number was correct and the label was false, which is the worse of the two failure modes: it invites the reader to conclude the cap is broken, and it would have taught an operator to distrust a control that works. It now shows "uncapped" when no collector is attached, with a tooltip explaining that the cap governs sending, and "cap N" only when sends are actually held to it. The value is frozen when the run starts rather than read live from the form, so toggling the destination mid-run cannot relabel a run that is already emitting. Also regenerated the screenshots and rewrote the README caption, which had been explaining why the rate exceeded the cap it displayed. It no longer displays one. 4 new vitest tests. Both states were checked in the running UI as well as in jsdom: no collector renders "uncapped · window 11s", and connecting a loopback collector flips it to "cap 2000 · window 11s". 519 python tests, 68 frontend (64 before).
404SecNotFound
added a commit
that referenced
this pull request
Sep 1, 2026
…heel job (roadmap #14) (#96) * feat: CLI-first container image + robust CI technique count (roadmap #14) The pip-installable CLI already exists (console_scripts + [web] optional extra); this adds the container half and a robust build gate. - Dockerfile: a CLI-first image (python:3.12-slim, non-root, ENTRYPOINT replicant). Web UI deliberately excluded so a CLI evaluator does not build a React bundle to see one FortiGate line. .dockerignore keeps everything but the package out of the build context, including the in-tree backup git repos and the checked-in webui_dist. - CI 'container' job builds the image and drives the CLI inside it (the positive control the author cannot run locally: no Docker daemon on the dev Mac). A broken Dockerfile fails there rather than being merged on inspection. - README documents 'pip install replicant' / '[web]' and the container trial, noting the PyPI publish itself is a release step. Also fixes a regression this session introduced: the transferability-notes footer (item #5) made 'replicant list | grep -c REP-0' return 28, turning the wheel job red on main. Both the wheel and container checks now count DISTINCT technique ids (grep -oE 'REP-[0-9]{3}' | sort -u), which is what '24 techniques' means and is immune to the footer. * review: fix Linux bind-mount perms, make the no-assets guard real, correct docs From /code-review on PR #96, verified before fixing: 1. (main) The README's headline 'docker run -v ... run' example failed on native Linux: the non-root image (uid 10001) cannot write a host-owned bind mount. Now documents --user $(id -u):$(id -g), and a new CI step exercises exactly that bind-mounted run on the Linux runner (previously untested). 2/4. The Dockerfile/README claimed 'replicant web' serves its build-it page (it errors 'web dependencies missing', no fastapi) and called webui_dist 'checked-in' (it is gitignored). Both corrected. 3. The 'no web assets' CI guard could never fail: webui_dist is gitignored and the job never built it, so the image lacked it regardless of .dockerignore. The container job now builds the frontend first (like the wheel job), so the guard actually tests the .dockerignore exclusion; deleting that line now fails it. Finding 5 (the distinct-count one-liner duplicated across wheel and container jobs) left as-is: both correct, a shared composite action would over-engineer a one-liner.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the backlog item filed during #12.
The problem
The run readout printed
cap 2000on every run. The events-per-second cap is only enforced where there is an emitter to throttle (orchestrator.py, the send loop), so a dry run or a file-only run is not limited at all.The README screenshot showed the consequence plainly:
19098 events / secsitting directly besidecap 2000.The number was correct and the label was false. That is the worse of the two failure modes — it invites the reader to conclude the cap is broken, and would teach an operator to distrust a control that actually works. #12 papered over it with a caption explaining why the rate exceeded the cap; this removes the need for the caption.
The fix
cap 2000 · window 11suncapped · window 11suncappedcarries a tooltip: "The events-per-second cap governs sending. This run has no collector, so nothing throttles it and the rate can exceed the configured cap."The value is frozen when the run starts rather than read live from the form, so toggling the destination mid-run cannot relabel a run that is already emitting.
Verification
4 new vitest tests, including one asserting the explanation exists rather than leaving the reader to guess. 519 Python, 68 frontend (64 before), build clean.
Checked in the running UI as well as jsdom, since a label is exactly the kind of thing jsdom can confirm exists while it renders wrong:
uncapped · window 11s, with the tooltipcap 2000 · window 11s, tooltip changes to "Sends are held to 2000 events per second."Screenshots regenerated, and the README caption rewritten — it had been explaining why the rate exceeded a cap the UI no longer claims.
Note
The Collector card still reads
cap 2000 eps. That one is accurate in place: it states the configured setting alongside the collector it applies to, rather than claiming anything about a particular run.