/** * Freeze-time assertion grounding (#16): the LLM proposes intent-based checks, and everything frozen * is verified against the observed evidence — a hallucinated check is dropped, so replay stays * deterministic (invariant #4). */ import type { LlmClient } from "../ports.js"; import type { Assertion, Evidence, NetworkRequest } from "../types.js"; import type { UrlMatchOptions } from "../steps.js"; /** * Stamp assertions the STARTING state already satisfies (#137), judged against the baseline * evidence captured right after the entry goto — before any flow action. `request-status` is the * strongest case: the request log only grows, so a check a landing-page request satisfied can * never fail at replay. `navigated` is vacuous when its destination already matches the entry * URL. The guards (`no-failed-requests` / `no-console-errors`) hold on any clean start — they * carry the flag so a guards-only scenario fails closed at replay, but a flow action can still * break them, so they are not individually warned on. `expect`/`custom` need a judge the freeze * doesn't have and are never flagged. */ export declare function markVacuous(assertions: Assertion[], baseline: Evidence, benign?: readonly string[], /** The consumer's locale list — the same one the verdict judges `navigated` with. Judging vacuity * under the engine defaults instead cuts both ways: an injected prefix makes a check the entry * page already satisfies look discriminating, and a run the consumer would call reached gets * stamped as one that was not. */ urlMatch?: UrlMatchOptions): Assertion[]; /** * Ground the frozen scenario's assertions in what actually happened, not what the LLM * guessed — it would propose `navigated` even on a SPA that never navigates, making every * replay fail. Always check requests; add `navigated` only if the run truly navigated; * keep a proposed `request-status` ONLY if a captured request actually matches it (so a * hallucinated check can't fail every replay), frozen as that request's stable endpoint prefix * rather than the proposed substring (#172 — a run-specific id in the proposal would never * match again). `expect` (LLM-judged) is frozen only when * `semantic` is set — otherwise the freeze stays deterministic (invariant #4). * `benign` is the product's noise list (mirror of `RunScenarioOptions.benign`) — a marked * endpoint's failure never disqualifies a check. */ export declare function deriveAssertions(proposed: Assertion[] | undefined, evidence: Evidence, semantic: boolean, benign?: readonly string[], /** Reports each dropped proposal with why — a drop changed what the freeze checks, so the * trace surfaces it as a `gate` event instead of silence (spec/core/trace.md). */ onDrop?: (proposed: Assertion, reason: string) => void): Assertion[]; /** * The request behind an action the freeze could not express a check for, if the run performed one. * Found only when BOTH hold: no `request-status` proof survived grounding, and the FLOW (not the * entry page load — see `sinceRequest`) fired a successful, non-benign, same-site mutation whose URL * `hasStablePath` rejects — the SAME predicate grounding refuses a check with. Sharing it is the * point: a gate that asks a different question than the refusal it exists to cover leaves the gap * between the two answers passing silently, which is the failure this rule is for. * * Same-site, because third-party background posts (an analytics SDK batching to * `api2.amplitude.com/2/httpapi` on every route change) have exactly this shape and never prove the * app's action; they would arm this on every read-only flow of an instrumented app. Same-site * transport noise (`sockjs.shop.co/123/abc/xhr_send`) still arms it — loud and fixable from the * outside: the product marks the endpoint `benign`, the seam this engine already uses for * app-specific noise rather than guessing at it. * * The proof half is deliberately coarse and this is its limit: ANY surviving proof disarms the gate, * including one belonging to a different action than the unexpressible mutation. Pairing a proof to * the action it proves is a larger change (see the PR discussion), so the gate under-fires there. */ export declare function findUnprovenAction(evidence: Evidence, assertions: readonly Assertion[], opts?: { benign?: readonly string[]; /** Index into the cumulative request log where the flow's own traffic starts — everything the * entry page load fired is excluded, the same separation `markVacuous` makes with the baseline. */ sinceRequest?: number; /** Pages the flow was on, besides `finalUrl` — a request on one of their hosts, or a subdomain * of it, is the app's own (`onSiteOf`). */ pageUrls?: readonly (string | undefined)[]; }): NetworkRequest | undefined; /** * #16 — at the end of discover, ask the LLM to propose intent-grounded assertions so the freeze * carries meaningful checks beyond the default network guard ("passed but wrong"). The proposal is * grounded by `deriveAssertions`, so a hallucinated check is dropped and replay stays deterministic. */ export declare function proposeAssertions(llm: LlmClient, intent: string, evidence: Evidence, semantic: boolean): Promise;