import { type StopConditionObservation, type StopWhen } from "./stop-conditions.js"; export declare const TASK_FUNNEL_SCHEMA: "humanish.task-funnel.v1"; /** * One task in a protocol. It has two halves that belong to two different people, and keeping them * apart is the point of the type. * * `goal` belongs to the PARTICIPANT. It is what they are asked to do, in their language, and it is * the only half that reaches the prompt. * * `success` belongs to the RESEARCHER. It is how the task will be measured, and the participant * never sees it — a moderator does not read the success criterion aloud, because telling someone * how they will be judged changes what they do. A persona told "you succeed when the URL contains * /dashboard" will go find that URL, which measures the instruction rather than the product. */ export interface LabTask { /** Stable id, used in evidence and in the funnel. Researcher-facing. */ id: string; /** PARTICIPANT-FACING. What they are asked to do. The only half that reaches the prompt. */ goal: string; /** RESEARCHER-FACING. Observation-shaped proof the task happened. Never rendered to the * participant. Absent means the task is narrative-only: it is asked for but cannot be measured, * and the funnel says so rather than quietly counting it as failed. */ success?: StopWhen; } /** When a task was first observed complete. */ export interface TaskCompletion { id: string; /** Turn number on which the criterion was first satisfied. */ turn: number; /** Which rule matched, for a reader who wants to know what counted as proof. */ matchedRuleIndex: number; matchedKinds: string[]; } /** The result a researcher actually wants: how far each participant got. */ export interface TaskFunnel { schema: typeof TASK_FUNNEL_SCHEMA; /** Declared tasks, in order. */ total: number; /** Tasks observed complete. */ completed: number; /** Declared tasks with no `done` criterion — shown to the participant, never observable. */ unobservable: number; /** The first task that was NOT observed complete: where this participant stopped. */ stoppedAt?: string; /** Per-task, in declaration order. `inputsObserved` is false when the task declares criteria * whose inputs (url, text, appState) NEVER arrived in any observation this session, so the * task was never actually measured. Absent when the task completed or is unobservable. */ tasks: Array<{ id: string; completed: boolean; observable: boolean; turn?: number; inputsObserved?: boolean; }>; /** Observable tasks that were never measured, because the inputs their criteria need never * arrived. Reported SEPARATELY from failures: a task nobody could observe is a gap in our * instrument, and reporting it as 0-completed blames the participant for it (#514). */ unmeasured: number; } /** * Tracks task completion across a session. Stateful on purpose: a task completes ONCE, on the first * observation that satisfies it, and stays complete even if the participant navigates away — you do * not un-sign-up by going back to the home page. */ export declare class TaskTracker { private readonly tasks; private readonly completions; /** Which observation fields were ever populated this session. A criterion whose field never * arrived was never evaluated against anything, however many turns ran (#514). */ private readonly fieldsSeen; constructor(tasks: readonly LabTask[]); /** Evaluate every still-incomplete task against one observation. Returns newly completed tasks. */ observe(observation: StopConditionObservation, turn: number): TaskCompletion[]; /** Which observation fields a task's criteria actually read. A task whose rules need `url` was * never measured if no observation ever carried one. */ private fieldsRequiredBy; /** The funnel as it stands. */ funnel(): TaskFunnel; } /** * The task list as the PARTICIPANT reads it: numbered, in order, in their own language. * * Reads `goal` and nothing else. The success criteria are the researcher's instrument and never * appear here — a participant who is told how they will be measured optimizes for the measurement, * and the study stops being about the product. A test pins this, because it is the kind of leak a * later convenience change makes without noticing. */ export declare function renderTaskPrompt(tasks: readonly LabTask[]): string | undefined; /** One line a stakeholder can read, with the denominator attached. */ export declare function formatTaskFunnel(funnel: TaskFunnel): string;