/** * Shape of a single prior step's recorded data, addressed by stepId. */ interface StepContextEntry { outputs?: any; } /** * Inputs for building a condition-evaluation context. */ interface BuildConditionContextArgs { /** * The already-mapped platform value ("linux" | "mac" | "windows"), i.e. the * same value runtime code stores on `context.platform` (via platformMap). */ platform?: string; /** The CURRENT step's outputs object (e.g. { exitCode, stdio, response }). */ outputs?: any; /** Map of prior steps' data keyed by author-set stepId: { [stepId]: { outputs } }. */ steps?: Record; } /** * The object that conditions / assertions are evaluated against. The locked * `$$` meta-value namespace resolves against this: * - `$$platform` -> `platform` * - `$$outputs.*` -> `outputs.*` * - `$$steps..outputs.*` -> `steps[stepId].outputs.*` */ interface ConditionContext { platform: string | undefined; outputs: any; steps: Record; } /** * Builds the context object a condition/assertion is evaluated against. * * Tiny and defensive: missing/undefined `outputs` or `steps` default to `{}` so * a condition that references a not-yet-run step (or an absent output) resolves * to `undefined` and the condition fails CLOSED via `evaluateAssertion` rather * than throwing. * * @param args - Optional `{ platform, outputs, steps }`. * @returns `{ platform, outputs, steps }` ready for `evaluateAssertion`. */ declare function buildConditionContext(args?: BuildConditionContextArgs): ConditionContext; /** * A single applicable implicit-assertion spec: a `$$` runtime expression plus * the severity to record when it evaluates false. `severity` defaults to * "fail" when omitted. */ interface ImplicitAssertionSpec { statement: string; severity?: "fail" | "warning"; } /** * One emitted assertion record. `statement` is the runtime expression that was * (or would have been) evaluated. Under the unified model `expected`/`actual` * are vestigial and omitted here. `source` distinguishes engine-emitted * ("implicit") records from author-written ("custom") `step.assertions`. */ interface ImplicitAssertionRecord { statement: string; source: "implicit" | "custom"; result: "PASS" | "FAIL" | "WARNING" | "SKIPPED"; } /** * Options for the shared evaluator. Defaults reproduce the original * implicit-only behavior so the 8 existing call sites are untouched. */ interface EvaluateAssertionsOptions { /** Stamped onto every emitted record's `source`. Defaults to "implicit". */ source?: "implicit" | "custom"; /** * When true, the evaluator starts in the short-circuited state: the FIRST * spec (and every later one) is recorded SKIPPED without evaluation. Used by * custom assertions to CONTINUE an implicit short-circuit — when an implicit * check already FAILed, the custom checks are not meaningful to assert on. */ startFailed?: boolean; } /** * Evaluate an ordered list of APPLICABLE implicit-assertion specs against a * condition context, in order, through the shared `evaluateAssertion` engine. * * Short-circuit semantics: once any spec evaluates to FAIL, every later spec is * recorded as SKIPPED (not evaluated) — its inputs may no longer be meaningful. * A false WARNING-severity spec records WARNING and does NOT short-circuit. The * step status is the FAIL > WARNING > SKIPPED > PASS roll-up (empty -> PASS). * * @param specs - Ordered, already-applicable specs. * @param context - A `buildConditionContext(...)` output. * @param options - Optional `{ source, startFailed }` (see * `EvaluateAssertionsOptions`). Defaults reproduce implicit-only behavior. * @returns `{ assertions, status }`. */ declare function evaluateImplicitAssertions(specs: ImplicitAssertionSpec[], context: ConditionContext, options?: EvaluateAssertionsOptions): Promise<{ assertions: ImplicitAssertionRecord[]; status: string; }>; /** * The CURRENT step as seen by the custom-assertion helper. Only `assertions` is * read here; it is the author condition form (string | string[]). An * array-of-objects (the report shape) is tolerated by the type but ignored at * runtime — it is not author input. */ interface CustomAssertionStep { assertions?: string | string[] | unknown[]; } /** * The action's result the helper folds custom records into (and mutates). * `status` is the rolled-up verdict; `assertions` holds any prior (implicit) * records; `outputs` is the per-action computed-output bag conditions read. */ interface CustomAssertionActionResult { status: string; assertions?: ImplicitAssertionRecord[]; outputs?: any; [key: string]: any; } /** * Evaluate the author-written `step.assertions` (the "custom" condition form) * AFTER an action has run, folding the results into `actionResult`. * * This is the runner-facing helper for custom assertions. It is strictly * additive: a step with no usable `assertions` field is left byte-identical. * * Contract: * - Only the condition form is evaluated: a string or an array of strings * (AND across the array). An array-of-objects (the report shape) is IGNORED * — it is not author input. * - Custom assertions are EVALUATED (and the status re-rolled) ONLY when the * action's status is PASS or WARNING. For ANY other status (FAIL for any * reason — execution error, an implicit FAIL record, etc. — or SKIPPED) the * custom checks are emitted as SKIPPED, NOT evaluated, and the action's * original status is PRESERVED (no re-roll). This guarantees custom * assertions can only ADD a failure to a passing/warning step, never * rescue/upgrade a failing or skipped one. * - Custom assertions are FAIL-only (no WARNING). An unresolvable `$$` fails * closed to FAIL (via `evaluateAssertion`). * - Custom records are appended to `actionResult.assertions` and, when * evaluated, `actionResult.status` is re-rolled across ALL records. * * Cross-step `$$steps.*` in custom assertions is DEFERRED: `steps` is passed as * `{}` here (resolution would fail closed to FAIL today). * * @param args - `{ step, actionResult, platform }`. `platform` is the mapped * `context.platform` value (or undefined). * @returns The same (mutated) `actionResult`, for convenience. */ declare function evaluateCustomAssertions(args: { step?: CustomAssertionStep; actionResult?: CustomAssertionActionResult; platform?: string; }): Promise; /** * The author form of a guard `if`: a single condition string, or an array of * condition strings that are AND-ed together (all must be truthy). */ type GuardCondition = string | string[]; /** * Evaluate a guard `if` against a condition context. * * Used at spec, test, and step scope. The guard decides whether the unit runs * at all (it is evaluated BEFORE the unit). Semantics: * - `undefined`/empty (no usable conditions) -> `true` (guard absent; run). * - A single string -> the truthiness of that one condition. * - An array of strings -> AND across all of them: `true` only if EVERY * condition is truthy. Evaluation short-circuits on the first falsy one. * - Each condition is evaluated through `evaluateAssertion`, which fails * CLOSED: an unresolvable `$$` reference resolves to `false` (so a guard * that references a not-yet-available value blocks the unit rather than * throwing). * * Non-string array entries are ignored (filtered out), and string entries are * trimmed with empty/whitespace-only ones dropped — only non-empty string * conditions are author input. If normalization leaves no conditions, the * guard is treated as absent (`true`). So `""`, `" "`, and `["", " "]` all * mean "guard absent", not "a falsy condition". * * @param ifValue - The author `if` value (`string | string[]` or undefined). * @param context - A `buildConditionContext(...)` output. * @returns `true` if the unit should run, `false` if it should be skipped. */ declare function evaluateGuard(ifValue: GuardCondition | undefined | null, context: ConditionContext): Promise; /** * Authoring-time detector: does a guard `if` value reference `$$steps.*`? * * `$$steps..outputs.*` is only populated at STEP scope (the per-step * accumulator in `runContext`). A spec- or test-level guard that references it * resolves against an empty `steps` map and fails closed — so the unit is * always skipped. Callers use this to emit an authoring warning rather than * letting the misuse silently swallow the unit. Non-string entries are ignored. * * @param ifValue - The author `if` value (`string | string[]` or undefined). * @returns `true` if any string condition references `$$steps.`. */ declare function guardReferencesSteps(ifValue: GuardCondition | undefined | null): boolean; /** * Authoring-time detector: does a step's custom `assertions` reference * `$$steps.*`? * * Parity sibling of `guardReferencesSteps`. `evaluateCustomAssertions` * evaluates custom assertions with `steps: {}` (cross-step `$$steps.*` is * deferred), so any `$$steps.*` reference resolves against an empty map and * fails closed — turning a passing step into a FAIL with no explanation. * Callers use this to emit an authoring warning instead of letting the misuse * silently fail the step. Only the author string form (`string | string[]`) is * inspected; the report shape (array of objects) is ignored. * * @param step - The step whose `assertions` field is inspected. * @returns `true` if any string assertion references `$$steps.`. */ declare function customAssertionsReferenceSteps(step: { assertions?: unknown; } | undefined | null): boolean; /** * A step's result status, used to select the matching routing handler. */ type StepRoutingStatus = "PASS" | "FAIL" | "WARNING" | "SKIPPED"; /** * A retry spec as authored on a routing entry. */ interface RetrySpec { limit: number; delay?: number; backoff?: "fixed" | "exponential"; } /** * The control-flow decision produced by resolving a step's routing handler. * `continue` runs the next step; `stop` halts the unit at the given scope; * `retry` re-runs the step (the runtime loops, then re-resolves with * `skipRetry` to get the terminal decision); `goToStep` jumps execution to the * step with the given stepId; `goToTest` jumps execution to the test with the * given testId (test-scope jumps are deferred to a later phase, but the variant * is in the union now so resolvers can name it). */ type RoutingDecision = { action: "continue"; } | { action: "stop"; scope: "test" | "spec" | "run"; } | { action: "retry"; limit: number; delay: number; backoff: "fixed" | "exponential"; } | { action: "goToStep"; stepId: string; } | { action: "goToTest"; testId: string; }; interface RoutingEntry { if?: GuardCondition; continue?: true; stop?: "test" | "spec" | "run"; retry?: RetrySpec; goToStep?: string; goToTest?: string; } /** * Resolve a step's routing handler for a given result status into a * control-flow decision. * * Selects the handler array for the status (`onPass`/`onFail`/`onWarning`/ * `onSkip`), then returns the FIRST entry whose `if` selector matches (an entry * with no `if` always matches; `if` is evaluated by `evaluateGuard`, which AND-s * an array and fails CLOSED). The matched entry maps to a decision: * - `{ continue: true }` -> `{ action: "continue" }` * - `{ stop: }` -> `{ action: "stop", scope }` * - `{ retry: {...} }` -> `{ action: "retry", limit, delay, backoff }` * (delay defaults to 0, backoff to "fixed") * - `{ goToStep: }` -> `{ action: "goToStep", stepId }` * - goToTest (not implemented this phase) -> the status default * * If the handler is absent/empty or no entry matches, the status DEFAULT is * returned. Defaults reproduce today's behavior (FAIL stops the test; PASS, * WARNING, and SKIPPED continue), so an unrouted step is byte-identical to the * pre-routing runner. flow != verdict: this only chooses control flow, never * the step's result. * * `skipRetry` makes a matched `retry` entry behave as a non-match (skip to the * next entry / fall to the default). The runtime uses it once retries are * exhausted to find the terminal action — so `onFail:[{retry},{continue}]` * means "retry, then continue", and `onFail:[{retry}]` means "retry, then the * default (stop)". * * @param args.status - The step's result status. * @param args.step - The step (read for `onPass`/`onFail`/`onWarning`/`onSkip`). * @param args.context - A `buildConditionContext(...)` output for `if` selectors. * @param args.skipRetry - Treat `retry` entries as non-matching (post-exhaustion). * @returns The control-flow decision. */ declare function resolveStepRouting(args: { status: StepRoutingStatus; step: { onPass?: RoutingEntry[]; onFail?: RoutingEntry[]; onWarning?: RoutingEntry[]; onSkip?: RoutingEntry[]; }; context: ConditionContext; skipRetry?: boolean; }): Promise; /** * Resolve a TEST's routing handler for a given rolled-up status into a * control-flow decision, used by the routed-spec sequencer to decide what * happens AFTER a test finishes. * * Mirrors `resolveStepRouting` but reads the test's `onPass`/`onFail`/ * `onWarning`/`onSkip` handlers and the test's rolled-up status, and uses the * same `ROUTING_BY_STATUS` defaults (FAIL -> stop(test); PASS/WARNING/SKIPPED -> * continue). The selector context is a `buildConditionContext(...)` output; only * `$$platform` is meaningful at test scope (tests aren't sequenced relative to * each other, so cross-test `$$outputs`/`$$steps` carry no data). * * Action mapping at test scope: * - `{ continue: true }` -> `{ action: "continue" }` * - `{ stop: }` -> `{ action: "stop", scope }` * The sequencer interprets these scopes: `stop:test` is a NO-OP (the test * already finished), `stop:spec` stops the spec's remaining tests, and * `stop:run` is deferred (warned once and treated as `spec` this phase). * - `{ goToTest: }` -> `{ action: "goToTest", testId }` (trimmed). The * sequencer jumps to that test within the spec (unknown target -> a FAIL * marker + stop, bounded by a per-spec visit cap). * - `{ retry }` / `{ goToStep }` -> not applicable at test scope; treated as a * matched-but-unhandled action -> the status default (stop scanning). * * If the handler is absent/empty or no entry matches, the status DEFAULT is * returned, so a test with no routing fields resolves to the same decision the * pre-routing flat pool implied. flow != verdict: this only chooses control * flow, never the test's result. * * @param args.status - The test's rolled-up result status. * @param args.test - The test (read for `onPass`/`onFail`/`onWarning`/`onSkip`). * @param args.context - A `buildConditionContext(...)` output for `if` selectors. * @returns The control-flow decision. */ declare function resolveTestRouting(args: { status: StepRoutingStatus; test: { onPass?: RoutingEntry[]; onFail?: RoutingEntry[]; onWarning?: RoutingEntry[]; onSkip?: RoutingEntry[]; }; context: ConditionContext; }): Promise; /** * The wait (ms) before a retry attempt. `retryIndex` is 0-based (0 = first * retry). `fixed` backoff waits `delay` every time; `exponential` waits * `delay * 2^retryIndex`. Returns 0 when delay is 0/undefined, and never * exceeds `MAX_RETRY_DELAY_MS`. * * @param delay - Base delay in milliseconds. * @param backoff - `"fixed"` or `"exponential"`. * @param retryIndex - 0-based retry index. * @returns The wait in milliseconds (0 .. MAX_RETRY_DELAY_MS). */ declare function computeRetryDelay(delay: number | undefined, backoff: "fixed" | "exponential", retryIndex: number): number; export { buildConditionContext, evaluateImplicitAssertions, evaluateCustomAssertions, evaluateGuard, guardReferencesSteps, customAssertionsReferenceSteps, resolveStepRouting, resolveTestRouting, computeRetryDelay, }; export type { BuildConditionContextArgs, ConditionContext, StepContextEntry, ImplicitAssertionSpec, ImplicitAssertionRecord, EvaluateAssertionsOptions, CustomAssertionStep, CustomAssertionActionResult, GuardCondition, StepRoutingStatus, RoutingDecision, RoutingEntry, RetrySpec, }; //# sourceMappingURL=routing.d.ts.map