/** * On-disk artifact constants: the project history, flow files, replay/drift status, the recorder * lifecycle, heal outcomes, and the structured-annotation vocabulary. */ /** * Schema version stamped into project.json so a reader can reject/upgrade old files. */ export declare const PROJECT_FILE_VERSION = 1; /** * Structured outcome when reading project.json fails (never thrown to the agent). * Mirrors ContractReadError. NOTE: recordRun self-heals a MALFORMED file (starts fresh) so a * corrupt history never wedges the agent; only the READ path (reticle_project) surfaces MALFORMED. */ export declare const ProjectReadError: { readonly MISSING: "project-missing"; readonly MALFORMED: "project-malformed"; /** * Readable, and written by a version of Reticle this one does not know. * * Kept apart from MALFORMED because the two need opposite handling. A malformed file has nothing * left to lose, so recording a run repairs it. A file from another version is intact, and * repairing it would overwrite history a later release could still read. */ readonly WRONG_VERSION: "project-wrong-version"; }; export type ProjectReadError = (typeof ProjectReadError)[keyof typeof ProjectReadError]; /** How a run record was produced. */ export declare const RunKind: { readonly FLOW_REPLAY: "flow_replay"; readonly MANUAL: "manual"; }; export type RunKind = (typeof RunKind)[keyof typeof RunKind]; /** * The persisted outcome of a run. Distinct from ReplayStatus (a wire/replay * concept of ok|drift|error): RunStatus is the history concept and adds pass/fail. The replay * site maps ReplayStatus.OK→PASS, DRIFT→DRIFT, ERROR→ERROR. */ export declare const RunStatus: { readonly PASS: "pass"; readonly DRIFT: "drift"; readonly ERROR: "error"; readonly FAIL: "fail"; }; export type RunStatus = (typeof RunStatus)[keyof typeof RunStatus]; /** * Bounds on project.json so the file stays small + diffable. recordRun keeps the * last PER_NAME runs of any single name, then caps the whole list to TOTAL most-recent overall. */ export declare const PROJECT_RUN_CAP: { readonly PER_NAME: 50; readonly TOTAL: 200; }; /** Maximum distinct route identities retained in project.json. */ export declare const PROJECT_ROUTE_CAP = 200; /** * Schema version STAMPED onto a flow file this Reticle writes. * * 2 since `FlowStep.expect` became a `Predicate`. Version 1 stored one flat slot per kind, which * could not hold a composite, a negation or a property assertion — an `allOf[netA, netB]` saved as * one of the two arms and the other was gone with nothing said. */ export declare const FLOW_FILE_VERSION = 2; /** Stamped instead of FLOW_FILE_VERSION on a flow that holds a `compare` — see flowFileVersionFor. */ export declare const COMPARE_FLOW_FILE_VERSION = 3; /** * Every version this Reticle can READ, which is not the same question as what it writes. * * A flow file is committed to a repository that other people share, so a reader that refused the * previous format would break a teammate's suite on the day one person upgraded. v1 is lifted on * read by `flowExpectToPredicate` and is NEVER rewritten: the file on disk stays v1 until something * saves it again, at which point it is stamped with the version above. * * A number outside this set is the wrong READER, not a damaged file, and says so. */ export declare const READABLE_FLOW_VERSIONS: ReadonlySet; /** * The version a flow file must carry for a reader to be able to replay it. * * A reader from before `compare` knew versions 1 and 2, so a v2 file holding one failed there as a * generic PARSE_FAILED on the unknown kind. Stamping only the flows that USE it keeps every other * flow readable by that older reader, and turns the rest into "the reader is the wrong one". * Every save routes through it, so a v1 file without one keeps its version byte for byte. * * The walk is over the whole value rather than the four fields a predicate lives in today (step * `expect`, `success`, `requires`, `ensures`), so a predicate slot added later cannot be missed. * ponytail: a literal `{ kind: "compare" }` inside an `equals` value also bumps it; that only costs * the older reader a file it could have read. */ export declare function flowFileVersionFor(flow: { readonly version: number; }): number; /** How a flow step is anchored to the live DOM at replay time (semantic, never a volatile ref). */ export declare const AnchorKind: { readonly TESTID: "testid"; readonly ROLE: "role"; readonly SIGNAL: "signal"; readonly COMPONENT: "component"; }; export type AnchorKind = (typeof AnchorKind)[keyof typeof AnchorKind]; /** * The role marker for a DEGRADED step — one recorded with no resolvable * testid. It is kept (never dropped) but a volatile eXX ref is NEVER persisted in its place; * the step carries this placeholder ROLE anchor + degraded:true, a legible "add a data-testid * here" marker that a human/self-healing pass re-binds. Satisfies the anchor min(1). */ export declare const DEGRADED_ANCHOR_ROLE = "unresolved"; /** * The JSON replacer every flow is written and sent with: a predicate flag at its default is left * out. An older platform's schema rejects a key it does not know (`ignoreCase` on a text check was * refused on every sync), so a flow that never asked for the flag must not carry it; only a check * that really is case-insensitive depends on a platform that knows the word. */ export declare function flowJsonReplacer(key: string, value: unknown): unknown; /** * How a missing flow is said, so a reader of a replay's error can tell "not on this computer" from * "the journey failed": a whole-app run called missing flows "regressions in journeys that used to * pass". */ export declare const NO_SUCH_FLOW = "no such flow on disk"; /** * A flow that calls one not on disk stops there, having never run: said with `NO_SUCH_FLOW`, so a * plan does not call a flow that never passed a "regression in a journey that used to pass". */ export declare const invokedFlowMissing: (name: string) => string; /** Structured failure codes for flow disk ops (returned, never thrown as free strings). */ export declare const FlowErrorCode: { readonly INVALID_NAME: "flow_invalid_name"; readonly NOT_FOUND: "flow_not_found"; readonly PARSE_FAILED: "flow_parse_failed"; /** * The file is well-formed and carries a `version` this build does not read. * * Distinct from PARSE_FAILED because the two need OPPOSITE fixes, and saying the wrong one costs * somebody a search for damage that is not there. Malformed means "repair or regenerate this * file". Wrong version means the file is fine and the READER is the wrong one -- upgrade or * downgrade Reticle, and do not touch the flow. * * `ProjectReadError.WRONG_VERSION` above draws the same line for `project.json` and for the same * reason; flows simply had no equivalent, so every future format bump would have reported itself * as corruption to every reader already in the field. */ readonly WRONG_VERSION: "flow_wrong_version"; readonly NO_RECORDING: "flow_no_recording"; readonly STEP_NOT_FOUND: "flow_step_not_found"; }; export type FlowErrorCode = (typeof FlowErrorCode)[keyof typeof FlowErrorCode]; /** A flow name must be a single safe path segment (no '/', '\\', '..', leading dot). */ /** * ONE safe path segment: the guard for anything that becomes a single directory or file name. * * Session ids and run ids are this. They are joined into `.reticle/sessions//` and written to, * and a session LABEL is supplied by the tab — so a separator here is a tab choosing where on disk * Reticle writes. Flow names were this too until they became namespaced, and the two guards shared * one pattern; widening the flow pattern silently widened these, which a test caught immediately. * They answer different questions and now have different patterns: a flow name may be a path, a * session id may never be one. */ export declare const SAFE_SEGMENT_PATTERN: RegExp; /** * A flow name: one or more `/`-separated segments. * * Namespaced because the replay grammar addresses documents by path — `onboarding/signup` is how a * composite names the sub-journey it invokes, and the protocol's `FlowNameSchema` has said so since * grammar v2. This pattern is the PERSISTENCE half, and while it stayed single-segment * `reticle_flow_save` refused every name the language is built on. Found by driving a composite: * recording worked, closing the boundary worked, saving it was refused. * * Traversal is impossible by CONSTRUCTION rather than by a filter. No segment may contain a dot at * all, so `..` cannot be spelled; every segment must START with a letter or digit, so a leading, * trailing or doubled separator cannot either. That is stronger than checking for `..`, because * there is no encoding of it left to miss. */ export declare const FLOW_NAME_PATTERN: RegExp; /** * The outcome of replaying an on-disk flow by re-resolving its * semantic anchors against the live DOM. `drift` (an anchor missed → contract changed) is * cleanly separated from `error` (the flow could not load or an action failed) and `ok`. */ export declare const ReplayStatus: { readonly OK: "ok"; readonly DRIFT: "drift"; readonly ERROR: "error"; readonly UNVERIFIABLE: "unverifiable"; }; export type ReplayStatus = (typeof ReplayStatus)[keyof typeof ReplayStatus]; /** * Why an anchor failed to resolve at replay time (the "whose fault is * it" reason kind). Drives the human `reason` sentence and whether a nearest-match is offered. */ export declare const DriftReason: { readonly TESTID_NOT_FOUND: "testid_not_found"; readonly SIGNAL_NOT_OBSERVED: "signal_not_observed"; readonly COMPONENT_NOT_FOUND: "component_not_found"; readonly STATE_MISMATCH: "state_mismatch"; /** * The step carries DEGRADED_ANCHOR_ROLE — no anchor was ever resolvable, so nothing was queried. * Distinct from TESTID_NOT_FOUND deliberately: "your element disappeared" and "this step never had * an element bound to it" need different fixes, and reporting the second as the first sent heal * hunting for the nearest testid to the literal word "unresolved". */ readonly ANCHOR_DEGRADED: "anchor_degraded"; /** * The anchor resolved to MORE THAN ONE live element, so which one the recording meant is no * longer decidable. * * Replay used to take the first match, act on it, and return `ok: true` carrying a note -- * "ambiguous testid 'x', used first match". The verdict is computed from `drift` and `ok` alone, * so the note never reached it: a replay that clicked row 3 instead of row 1 reported `ok`. * That is a locator resolving to a different element than the one recorded, reported green, * which is the one thing a replay must never do -- its entire claim is "it did what it did * before". * * Distinct from TESTID_NOT_FOUND for the same reason ANCHOR_DEGRADED is, and the distinction is * what makes it fixable: "your element disappeared" wants a RENAMED anchor, and "your element is * now several elements" wants a NARROWER one. Heal can only propose the second if the reason * says which happened. */ readonly ANCHOR_AMBIGUOUS: "anchor_ambiguous"; /** * The step's anchor resolved and its action ran; the testid its `expect.element` names was absent * afterwards. Distinct from TESTID_NOT_FOUND for the same reason ANCHOR_DEGRADED is: "the element * you clicked is gone" and "the thing you asserted afterwards never appeared" need different * fixes, and reporting the second as the first sends the caller looking for a renamed anchor on a * step whose anchor was fine. */ readonly EXPECT_ELEMENT_NOT_FOUND: "expect_element_not_found"; /** * The step's `expect.net` named a request that was still ON THE WIRE when the budget ended. * * A blind spot, not a failed assertion. Reported as SIGNAL_NOT_OBSERVED it reads as "the request * you declared never happened", which sends a reader hunting a feature that works — the live * verdict path already refuses to grade a named in-flight request as a miss, and replay was the * one reader that had not asked the question. The fix for this drift is a longer budget or a * faster endpoint; the fix for SIGNAL_NOT_OBSERVED is the handler behind the action. */ readonly NET_STILL_IN_FLIGHT: "net_still_in_flight"; }; export type DriftReason = (typeof DriftReason)[keyof typeof DriftReason]; /** * Did the step's anchor resolve and its action RUN, leaving only the declared consequence unmet? * * The three reasons below all mean the same thing about the app's state: the element was found, the * action was dispatched, and the page is where the step left it. Only the assertion failed. The * other reasons mean the anchor itself was never resolved, so the page is somewhere the flow never * described and every later step would run against a state nobody predicted. * * That difference is what lets a replay CONTINUE past a failure without turning one wrong result * into several — see the sweep option on replayFlow. Halting on both was correct while there was no * way to tell them apart; it stopped a 6-step bug sweep at its first defect and reported the other * five as `notAttempted`. */ export declare function isConsequenceDrift(reason: DriftReason): boolean; /** Default timeout (ms) a signal anchor waits to be observed at replay. */ export declare const FLOW_SIGNAL_TIMEOUT_MS = 4000; /** * The structured annotation kinds a human can attach while recording. * FIRST CUT: only these four structured kinds compile to flow fields (via a toolbar menu + * a signal