Skip to content

fix: stop the run readout claiming a cap it is not applying - #14

Merged
404SecNotFound merged 1 commit into
mainfrom
fix/eps-cap-label
Jul 29, 2026
Merged

fix: stop the run readout claiming a cap it is not applying#14
404SecNotFound merged 1 commit into
mainfrom
fix/eps-cap-label

Conversation

@404SecNotFound

Copy link
Copy Markdown
Owner

Closes the backlog item filed during #12.

The problem

The run readout printed cap 2000 on 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 / sec sitting directly beside cap 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

Run Readout
Sending to a collector cap 2000 · window 11s
Dry run or file-only uncapped · window 11s

uncapped carries 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:

  • no collector → uncapped · window 11s, with the tooltip
  • connect a loopback collector and toggle the destination → cap 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.

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
404SecNotFound merged commit 171802d into main Jul 29, 2026
9 checks passed
@404SecNotFound
404SecNotFound deleted the fix/eps-cap-label branch July 29, 2026 07:29
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant