Skip to content

Fix an unresolved import, and declare the reporting capability - #157

Merged
patchstackdave merged 3 commits into
mainfrom
eng-3586-detection-capability
Aug 20, 2026
Merged

Fix an unresolved import, and declare the reporting capability#157
patchstackdave merged 3 commits into
mainfrom
eng-3586-detection-capability

Conversation

@patchstackdave

Copy link
Copy Markdown
Contributor

Two things, and the first is a bug on main right now.

reportDetections: true throws on main

runtime.js calls createDetectionReporter and never imports it. The feature is opt-in, so nobody reached the failure — including the tests: they exercised the reporter directly, or asserted that nothing is posted when the feature is off. Neither path enters the branch that builds the reporter.

A feature that only breaks for whoever turns it on, guarded by tests that never turn it on. It would have surfaced the moment the default was flipped, which was the next planned change.

Closed with the test that was missing: switch reporting on, fire a matching request, assert a detection reaches the endpoint. Removing the import fails it — which is precisely what the previous suite could not do.

Declaring the capability

Detections are only sent when a rule fires, so silence at the server means one of three things — nothing matched, reporting is off, or reports are not arriving — and nothing distinguished them. A dashboard reading that silence has to guess, and guessing wrong means telling a correctly configured customer to enable something already on.

The rules fetch now carries X-Patchstack-Detections: enabled when reporting is on. That request is already periodic, already authenticated, and already identifies the site: no new outbound path, and no request data.

A capability, not a heartbeat. The header says "this guard reports"; the server records when it saw that. A client-supplied "alive as of" timestamp would be a value from outside dressed up as an observation, and a wrong or stale clock is exactly what it would fake.

Asserted in both directions — a guard with reporting on says so, and a guard without it stays silent. The second matters as much as the first: a declaration sent regardless tells the server nothing.

Verification

1252 tests, typecheck clean. Mutation-checked: removing the import fails the two tests that enter the enabled path, and nothing else.

**The fix first, because it is on main.** `runtime.js` calls `createDetectionReporter` and never
imported it, so `reportDetections: true` threw `ReferenceError` at boot. Being opt-in, nobody reached
it — including the tests: they exercised the reporter directly, or asserted that nothing is posted
when the feature is OFF, and neither path enters the branch that builds it. A feature that only fails
for whoever turns it on, guarded by tests that never turn it on.

Closed with the test that was missing: switch reporting on, fire a matching request, and assert a
detection reaches the endpoint. Removing the import fails it, which is what the previous suite could
not do.

**Then the capability.** Detections are sent only when a rule fires, so silence at the server means
one of three things — nothing matched, reporting is off, or reports are not arriving — and nothing
distinguished them. The rules fetch now carries `X-Patchstack-Detections: enabled` when reporting is
on: a request that is already periodic, already authenticated, and already identifies the site. No new
outbound path and no request data.

A capability, not a heartbeat. The header says "this guard reports"; the server records when IT saw
that. A client-supplied "alive as of" would be a value from outside dressed as an observation, and a
wrong or stale clock is exactly what it would fake.

Asserted in both directions: a guard with reporting on says so, and a guard without it stays silent.
The second matters as much — a declaration that is sent regardless tells the server nothing.

1252 tests, typecheck clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderbuds

coderbuds Bot commented Aug 20, 2026

Copy link
Copy Markdown

Well-explained addition of a reporting capability with comprehensive test coverage and clear structure.

🎯 Quality: 100% Elite · 📦 Size: Medium

📈 This month: Your 104th PR — above team average · Averaging Excellent

See how your team is trending →

patchstackdave and others added 2 commits August 20, 2026 14:44
The header went out whether or not a bearer token resolved. The rules endpoint still accepts a bare
UUID while token auth is optional, so on that path this was an assertion anyone holding the UUID could
make — and it asserts the reassuring thing: that reporting is on. A dashboard would then show a site
as covered because a stranger said so, which is the worst direction for a wrong answer to point.

Fetching rules must never hinge on getting a token — protection comes first, and that is unchanged.
Claiming a capability may: an unauthenticated request is one whose statements about a site carry no
weight, and there is nothing to lose by withholding a claim.

The server will gate on a verified token as well. A client-side check only removes the ACCIDENTAL
case — a guard on the legacy path telling the truth about itself — because the forgeable case is not
the client's to prevent. Both halves are needed and neither is sufficient.

Also fixed a test that was passing for the wrong reason: the positive case supplied no credential, so
it had been asserting the header on an unauthenticated request. It now exchanges a real one and checks
that the claim travels with `Bearer`, which is what the gate is about.

Mutation-checked: claiming it regardless of the credential fails the unauthenticated test and nothing
else. 1253 tests, typecheck clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… a guarantee

The comment asserted that the server verifies a token before accepting this
header. Server-side verification of the rules fetch is configurable, and nothing
consumes the header yet, so the sentence claimed a present guarantee where there
is a requirement on whatever eventually reads it.

State the requirement instead: a client-side gate removes the accidental case
only, and a consumer has to verify the token itself before believing the claim.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@patchstackdave

Copy link
Copy Markdown
Contributor Author

/review

@patchstackdave
patchstackdave merged commit 2e7229c into main Aug 20, 2026
6 checks passed
@patchstackdave
patchstackdave deleted the eng-3586-detection-capability branch August 20, 2026 13:49
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.

2 participants