/**
 * AP.4 — read-side primitives for the workflow gate.
 *
 *   - `has_active_task` — is there an active task this session, and what is it?
 *     Used by the workflow gate (rule #8) AND the scope→task Gate A (AP.5).
 *   - `workflow_phases_complete` — are all 7 REQUIRED phases logged for the
 *     CURRENTLY-active task? Computed at read time against the live active task
 *     (so a new task never inherits a prior task's completion — see
 *     workflow_phases.ts).
 *
 * Both are read-only, no args, never throw. GF.4 (F6): a read failure returns the
 * conservative `ok` verdict ("no active task" / "not complete" / "not generated"),
 * NEVER an `err` — an `err` rule result is silently skipped by the dispatcher
 * (dispatch.ts:462), which would let a blocking gate fall through to ALLOW (fail-open).
 * Returning a conservative `ok` makes the gate fail CLOSED. `memoizable: false` is
 * load-bearing: the active task + phase ledger change within a session, so a memoized
 * result would be stale.
 *
 * Imports from: zod, ../runtime/result.js, ../runtime/session_state.js,
 *   ../runtime/workflow_phases.js.
 * Imported by: src/runtime/bootstrap.ts (registry wiring).
 */
import { stat } from 'node:fs/promises';
import { dirname, isAbsolute, resolve } from 'node:path';
import { z } from 'zod';
import { readHarnessTasks } from '../runtime/hooks/active_task_mirror.js';
import { ok } from '../runtime/result.js';
import { readActiveTask } from '../runtime/session_state.js';
import { isComplete, readPhaseState } from '../runtime/workflow_phases.js';
const NoArgs = z.object({}).strict();
export const HasActiveTask = {
    name: 'has_active_task',
    argSchema: NoArgs,
    durable: false,
    memoizable: false,
    costEstimateMs: 2,
    execute: async (_args, ctx) => {
        try {
            const active = await readActiveTask(ctx.sessionId);
            if (active === null)
                return ok({ present: false, id: '', task_id: '' });
            return ok({ present: true, id: active.id, task_id: active.taskId ?? '' });
        }
        catch {
            // GF.4 (F6): fail CLOSED — a thrown read becomes the conservative verdict (no
            // active task), NEVER an `err` the dispatcher silently skips (dispatch.ts:462),
            // which would let a blocking gate fall through to ALLOW.
            return ok({ present: false, id: '', task_id: '' });
        }
    },
};
export const WorkflowPhasesComplete = {
    name: 'workflow_phases_complete',
    argSchema: NoArgs,
    durable: false,
    memoizable: false,
    costEstimateMs: 3,
    execute: async (_args, ctx) => {
        try {
            const active = await readActiveTask(ctx.sessionId);
            if (active === null)
                return ok({ active: false, complete: false });
            const state = await readPhaseState(ctx.sessionId);
            return ok({ active: true, complete: isComplete(state, active.id) });
        }
        catch {
            // GF.4 (F6): fail CLOSED — not complete on any read failure.
            return ok({ active: false, complete: false });
        }
    },
};
async function pathExistsAbs(p) {
    try {
        await stat(p);
        return true;
    }
    catch {
        return false;
    }
}
/**
 * Does the active task have GENERATOR PROVENANCE — a `docs/tasks` 11-field spec
 * the scope→task pipeline produced? Backs scope→task Gate A (AP.5): a code-write
 * whose active task has no generated spec is blocked.
 *
 * Provenance = `active-task.json.spec` (copied by the AP.1 mirror from the
 * harness `metadata.spec`) resolving to a real file. H7: the spec lives in the
 * umbrella's PLANNING repo while the code-write may run in a member SUB-repo,
 * so an ABSOLUTE `spec` is checked directly and a RELATIVE one is resolved
 * against the event cwd AND each of its ancestors (the planning repo is an
 * ancestor in every real layout; T-FIX-TASKSTART-GUARD-MIRROR — the old
 * cwd-only resolution dangled from sub-repo cwds and false-fired the FU.11
 * task_unscoped reset mid-flow). No active task OR no spec OR a path found
 * nowhere → not generated (the conservative verdict — Gate A blocks).
 */
export const HasGeneratedSpec = {
    name: 'has_generated_spec',
    argSchema: NoArgs,
    durable: false,
    memoizable: false,
    costEstimateMs: 4,
    execute: async (_args, ctx) => {
        try {
            const active = await readActiveTask(ctx.sessionId);
            if (active === null)
                return ok({ present: false, generated: false });
            const spec = active.spec;
            if (spec === undefined || spec === '')
                return ok({ present: true, generated: false });
            const cwd = ctx.event.kind === 'tool_call' ? (ctx.event.cwd ?? process.cwd()) : process.cwd();
            if (isAbsolute(spec)) {
                return ok({ present: true, generated: await pathExistsAbs(spec) });
            }
            // Relative: cwd first, then ancestors (bounded walk, same 64-hop bound
            // as resolveProjectScopeRoot). Found nowhere → conservative false.
            let dir = resolve(cwd);
            for (let i = 0; i < 64; i++) {
                if (await pathExistsAbs(resolve(dir, spec))) {
                    return ok({ present: true, generated: true });
                }
                const parent = dirname(dir);
                if (parent === dir)
                    break;
                dir = parent;
            }
            return ok({ present: true, generated: false });
        }
        catch {
            // GF.4 (F6): fail CLOSED — not generated on any read failure.
            return ok({ present: false, generated: false });
        }
    },
};
/**
 * Gate B (AP.5) read-side: does the ENTIRE open task list have generator
 * provenance? Walks the harness store (via AP.1's isolated reader) and flags any
 * pending/in_progress task lacking `metadata.taskId` — the stamp scope→task
 * generation applies (H6). Closes Gate A's loophole: a task smuggled into the
 * list manually (no spec, no pre-research) would otherwise let code through once
 * loaded. Completed/deleted tasks are ignored (only open work must be generated).
 */
export const TaskListGenerated = {
    name: 'task_list_generated',
    argSchema: NoArgs,
    durable: false,
    memoizable: false,
    costEstimateMs: 5,
    execute: async (_args, ctx) => {
        try {
            // ATM.2: THIS CC version keeps the task list in the session transcript,
            // not ~/.claude/tasks/. The UPS hook (which has transcript_path; this
            // function layer does not) derives the open-task list onto
            // `event.openTasks` for the prompt_submit Gate B fires on. Prefer it;
            // fall back to the harness-store read for older CC / non-UPS events.
            const ev = ctx.event;
            const open = ev.kind === 'prompt_submit' && ev.openTasks !== undefined
                ? ev.openTasks
                : (await readHarnessTasks(ctx.sessionId)).map((t) => ({
                    id: t.id,
                    status: t.status,
                    taskId: t.metadata?.taskId,
                }));
            const ungenerated = open
                .filter((t) => t.status === 'pending' || t.status === 'in_progress')
                .filter((t) => t.taskId === undefined || t.taskId === '')
                .map((t) => t.id);
            return ok({ all_generated: ungenerated.length === 0, ungenerated });
        }
        catch {
            // GF.4 (F6): fail CLOSED — treat an unreadable task store as "not all generated"
            // (Gate B blocks), never an `err` the dispatcher skips.
            return ok({ all_generated: false, ungenerated: [] });
        }
    },
};
/**
 * AF.6 — the number of OPEN (pending|in_progress) tasks. The pause-gates derive whether a
 * workflow RUN is still active from this + the FSM state (run-active = FSM≠idle AND
 * (count>0 OR FSM≠phases_complete)); the run auto-OFFs when the backlog is depleted.
 * Prefers `event.openTasks` (prompt_submit, transcript-derived by the UPS hook); falls
 * back to the harness store for older CC / non-UPS events. Never throws → count 0.
 */
export const OpenTaskCount = {
    name: 'open_task_count',
    argSchema: NoArgs,
    durable: false,
    memoizable: false,
    costEstimateMs: 2,
    execute: async (_args, ctx) => {
        try {
            const ev = ctx.event;
            const open = ev.kind === 'prompt_submit' && ev.openTasks !== undefined
                ? ev.openTasks
                : (await readHarnessTasks(ctx.sessionId)).map((t) => ({ id: t.id, status: t.status }));
            const count = open.filter((t) => t.status === 'pending' || t.status === 'in_progress').length;
            return ok({ count });
        }
        catch {
            return ok({ count: 0 });
        }
    },
};
//# sourceMappingURL=active_task.js.map