OK    valid task validates (exit 0)
OK    unknown task key exits 2 (exit 2)
OK    unsupported taskVersion exits 2 (exit 2)
OK    invalid task id exits 2 (exit 2)
OK    empty prompt exits 2 (exit 2)
OK    NUL in expectExact exits 2 (exit 2)
OK    out-of-range timeout exits 2 (exit 2)
OK    missing mission file exits 4 (exit 4)
OK    task with valid mission validates (exit 0)
OK    invalid mission exits 2 (exit 2)
OK    validate missing task exits 4 (exit 4)
OK    validation does not modify the task file
OK    prune dry-run exits 0 (exit 0)
OK    dry-run deleted nothing
OK    prune --keep=2 --yes removes oldest (exit 0)
OK    kept exactly 2 newest runs
OK    newest run kept, oldest pruned
OK    append-only receipt written (3 entries)
OK    sessions/workspaces untouched by prune
OK    prune with invalid keep exits 4 (exit 4)
OK    mock adapter: gate passes on matching mock response (exit 0)
OK    mock adapter: expect-mismatch recorded (exit 1)
OK    mismatch reason recorded
OK    unknown adapter fails closed (exit 1)
OK    mission task runs via mock adapter (exit 0)
OK    mission section injected into generated prompt
OK    retry of mission run succeeds (exit 0)
OK    retriedFrom lineage recorded
OK    mission section present after retry (relative path resolved)
OK    retry of missing run exits 4 (exit 4)
OK    skill install bundled ms-tools (exit 0)
OK    installed to skills-available
OK    double install refuses (exit 1)
OK    activate enables skill (exit 0)
OK    enabled dir populated
OK    uninstall while enabled refuses (exit 1)
OK    deactivate moves back to available (exit 0)
OK    uninstall removes from available (exit 0)
OK    seat with enabled skill launches
OK    skill path delivered to adapter
OK    shipped researcher contract resolves (ceiling + network)
OK    resolve-role refuses a non-role document (conductor policy) (exit 2)
OK    resolve-role refuses name/filename mismatch (exit 2)
OK    resolve-role refuses unknown network declaration (exit 2)
OK    resolve-role refuses duplicate role tool (exit 2)
OK    resolve-role refuses unsupported tool (exit 2)
OK    resolve-role refuses missing contract file (exit 4)
OK    version 1 role prints no contract line
OK    shipped version 2 roles resolve with their contracts (pm, cto, coder, reviewer)
OK    resolve-role accepts a minimal version 2 role (exit 0)
OK    resolve-role refuses an action outside the vocabulary (exit 2)
OK    resolve-role refuses a gated-only action in a role file (exit 2)
OK    resolve-role refuses a Vikunja verb no role may hold (exit 2)
OK    resolve-role refuses a version 2 role whose contract is missing (exit 2)
OK    resolve-role refuses a contract that is a symbolic link (exit 2)
OK    seat launches under role ceiling
OK    role ceiling narrows seat tools (read,write,bash -> read,bash)
OK    narrowing recorded loudly
OK    missing role contract refuses launch (exit 2)
OK    missing-contract refusal names the role
OK    empty ceiling intersection -> tool-free seat
OK    tool-free outcome recorded loudly
OK    overridden agents dir without seat definition refuses (exit 4)
OK    seat-resolution refusal names the seat
OK    policy: mission only -> mission tools
OK    policy: task only -> task tools
OK    policy: both -> intersection (task narrowed)
OK    policy: empty intersection -> tool-free
OK    invalid mission capabilities rejected (exit 2)
mosaic-task: unsupported mission tool: "sudo" (supported: read, write, edit, bash, grep, find, ls)
OK    task run with user layer present (exit 0)
OK    user context dispatched into generated prompt
OK    workspace+tools task runs via mock (exit 0)
OK    workspace path + tools delivered to adapter
OK    persistent workspace created on host
OK    plain task still runs (no workspace/tools) (exit 0)
OK    workspace var present but empty when absent
OK    tools empty when absent
OK    unknown tool exits 2 (exit 2)
OK    workspace traversal exits 2 (exit 2)
OK    live hello task succeeds with exact marker
OK    result.json written in run dir
OK    result.json contents are correct
OK    wrong expectExact fails with exit 1 (reason: expect-mismatch)
OK    each run gets a distinct run dir (no clobber)
OK    list shows both runs
OK    fork base: teach succeeds (exit 0)
OK    fork child recalls ancestor context (exit 0)
OK    forked child recalled ancestor code word
OK    ancestor session untouched by fork
OK    child session dir has its own branch file
OK    onboard without name exits 4 (non-interactive) (exit 4)
OK    onboard --name renders profile (exit 0)
OK    profile written
OK    canon structure: required filled, optional placeholdered
OK    canon sections present
OK    user recall run succeeds
OK    recalled user name (response: Jason)
OK    no agent identity on headless run

selftest: 98 passed, 0 failed
