/** * Per-step outcome capture: derive the grounded post-condition (`expect`) a step is frozen with, * so a step that runs but doesn't reach its outcome is caught at replay (and healed). Expects are * decided RETROACTIVELY at freeze time from the completed evidence (#81) — never from a mid-run * snapshot that races the step's own in-flight request. See spec/core/surgical-heal.md. */ import type { Driver } from "../ports.js"; import type { Evidence, NetworkRequest, Step, WaitUntil } from "../types.js"; import type { UrlMatchOptions } from "../steps.js"; /** host + path of a url (query/hash dropped) — a stable, meaningful destination to assert. */ export declare function destinationKey(url: string): string; /** What the loop records before each executed step: the page URL and the request-log length. The * log is append-only within a run (statuses update in place), so `[mark.requestCount, next mark)` * is exactly the tail of requests that step fired. */ export interface OutcomeMark { url: string | undefined; requestCount: number; } /** Observe the freeze-time evidence, waiting (bounded) while a mutation fired during the run is * still in flight — so retroactive expect/assertion grounding sees resolved statuses, not a race. */ export declare function observeOutcomes(driver: Driver, firstRequestCount: number): Promise; /** Retroactively attach each step's grounded post-condition from the completed evidence. * Navigation → expect that destination (the URL at the NEXT executed step, or the final URL — * nothing acts in between, so it is the page this step reached). Navigation is judged at * `destinationKey` granularity — the same granularity the expect is frozen at (#96): a query/hash-only * move would freeze a URL expect the PRE-navigation page already satisfies, so replay's idempotency * pre-check would silently skip the step; such a move falls through to the mutation expect (the fired * request is stronger evidence anyway). Else, a fresh successful mutation in the step's own request * tail → expect that request. A step that changed nothing stays unchecked — a weak expect would * trigger false divergence. `marks[i] === null` skips a step the loop doesn't verify (the baseUrl * goto). */ export declare function assignStepExpects(steps: Step[], marks: readonly (OutcomeMark | null)[], evidence: Evidence, /** The consumer's matching rules — replay pre-checks a frozen URL expect with these, so the * freeze has to ask "is this already satisfied?" under the same ones. Locale stripping only ever * makes matching MORE permissive, so freezing under the defaults while replay runs with an * injected prefix flips a discriminating expect into a pre-satisfied one, and the step is skipped. */ opts?: UrlMatchOptions & { benign?: readonly string[]; }): void; /** A `requestStatus` post-condition for a mutation request the step itself fired and that succeeded — * the request that proves the action, so replay can wait for it. Benign noise is excluded, the method * is frozen for exact matching (a same-path GET must not satisfy a submit), and the frozen path stops * before a run-specific id segment (which would never match on a later replay). A repeated identical * mutation (a second add-to-cart) still counts — the tail is positional, not a seen-set. */ export declare function freshMutationExpect(tail: NetworkRequest[], benign?: readonly string[]): WaitUntil | undefined; /** host + path cut at the first dynamic-looking segment (see `isDynamicSegment`) — a stable prefix * that still substring-matches the full request URL on a later replay, where a run-specific id * would never match again. Query and hash are dropped with the rest of the URL by `destinationKey`. * Shared with assertion grounding (#172) so a step expect and a `request-status` assertion freeze * the same endpoint identity. */ export declare function stableEndpointPrefix(url: string): string; /** * Do two request URLs name the same endpoint — same shape, differing only where the run mints * values? `/cart/add?ids=586738` and `/cart/add?ids=586739` do (one action, fired twice); * `/api/products` and `/api/products/586738` do not, nor do `/orders/1/confirm` and * `/orders/2/cancel`. Used to tell a widening that keeps the check's meaning from one that spends it. */ export declare function sameEndpointShape(a: string, b: string): boolean; /** * The destination to freeze for a `navigated` check (and a step's URL expect): host + path with * every run-minted segment replaced by `*`, which `urlReached` matches one-for-one. A URL with no * such segment freezes exactly as before. * * Why not the stable PREFIX used for request URLs: a request check matches by substring, so cutting * at the first id still matches the whole URL, but a destination is matched at a path boundary and * a parent never counts as reaching a deeper page — `shop.co/orders` would fail even against the * run that discovered `shop.co/orders/586738/done`. The wildcard keeps the depth and the segments * around the id, and pins the host either way. */ export declare function stableDestination(url: string): string; /** * Does a frozen destination name a page, or merely the area one lives in? `shop.co/*` is reached by * an error page and a login redirect alike, which is exactly what `navigated` exists to catch — and * `shop.co/app/*` is no better in an app that mounts login and errors under the same prefix. The * cost of this line is real: a list → detail step freezes no URL check, since `/products/586738` * generalizes to a wildcard leaf. Losing a check is the loud direction; keeping one that the error * page satisfies is the silent one. * * Caveat: a literal `*` is legal in a URL path and is not escaped here, so a page whose real path * contains one freezes as a wildcard and matches more than it did before. */ export declare function namesAPage(destination: string): boolean; /** Did any path survive the cut? A host-only prefix would be satisfied by every request to that * host, so it is refused rather than frozen — by the assertion path and the step expect alike. */ export declare function hasStablePath(prefix: string): boolean;