Skip to content

scraping: expect.maxDrop without history is silently inert, and nothing refuses the combination #154

Description

@sebyx07

What

packages/scraping/src/expect.tsmaxDrop compares a run's yield against a baseline that only exists if history: is declared. Declare expect: { maxDrop } without history: and the guard is inert: there is no baseline, so the alarm can never fire. Nothing refuses the combination at definition time, and nothing says so at runtime.

The two wrong fixes, and why

Record history unconditionally (the sweep's original proposal) — rejected with evidence. expect.test.ts:76-80 pins the current behaviour deliberately by name: "no expect declared is no alarm and no history written". And recording unchecked runs has a real cost: a stretch of silent zero-row runs would become the median a later maxDrop is measured against, so arming the guard later would arm it against a poisoned baseline.

Leave it as documentation — rejected by axiom 3. A convention that is not a build error does not exist, and this one is invisible: the scrape runs, the guard reports nothing, and the author believes they are protected.

The right fix

Refuse the combination where it is declared. expect.maxDrop present and history absent is a definition-time refusal with a stable code, a cause naming both fields, and an executable fix: — the shape assertIdempotencyScope and assertRateLimitScope already use elsewhere in the framework: a guarantee that cannot be shown to hold is refused where it is declared, rather than assumed at 3am.

Alternatively, make it unrepresentable in the type — history required whenever maxDrop is present — which is cheaper and catches it at compile time, with the runtime refusal as the JSON/JS backstop. That pairing is what @ultimat3/auth just did for mfa.required.

Why it was deferred

It needs a new X_SCRAPE_* error code, which needs a wiki/Error-Codes.md row and a regenerated framework.manifest.json. Both are coordinator-owned, and it surfaced during review of a security PR (#152) where scope growth was the wrong trade.

Raised by CodeRabbit on #152; the underlying gap was found by the multi-agent bug sweep, 2026-08-18.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions