/**
 * @internal
 * outputs-guard.mjs — task outputs allowlist enforcement (R11).
 *
 * Roundtable recommendation (Innovation Scout): every task record declares
 * the path set its lifecycle commits are allowed to touch. Before any
 * partial-commit staging (roadmap sync, team sync), we intersect the dirty
 * file list with the task's `outputs` globs. Anything outside the declared
 * path set fails loud with the offending path.
 *
 * This is a belt to R7's suspenders: even if a future refactor forgets to
 * route a git cwd to the worktree, the outputs guard catches the leak at the
 * contract level by rejecting paths that don't belong to this task.
 *
 * Precedent: Buck2 hermetic outputs, Kubernetes managedFields, Nix
 * allowedReferences. The glob matcher is deliberately tiny — hand-rolled to
 * avoid pulling in minimatch/picomatch for one use site.
 */
/**
 * Default outputs applied to brand-new tasks.
 *
 *   - `unlinked`: permissive `**` for tasks without milestone/phase linkage
 *     (quick tasks, investigations). The user opted out of lifecycle commits
 *     by not linking, so the allowlist is effectively unbounded.
 *   - `phase-linked`: the exact path set that lifecycle commits and
 *     bookkeeping writes need. Excludes the app source (apps/, packages/,
 *     etc.) so the lifecycle machinery cannot stage project code — those
 *     writes belong to the executor, not the framework.
 *
 * @internal
 */
export const DEFAULT_TASK_OUTPUTS = Object.freeze({
    unlinked: Object.freeze(['**']),
    'phase-linked': Object.freeze([
        '.aperant/tasks/<id>/**',
        // C11 — explicit entry for the per-task token telemetry file. Already
        // covered by the wildcard above, but listed by name so the artifact's
        // existence is discoverable when grepping the allowlist.
        '.aperant/tasks/<id>/tokens.json',
        '.aperant/tasks/INDEX.md',
        '.aperant/roadmap/<scope>/phases/<phase-id>.json',
        '.aperant/roadmap/<scope>/phases/**',
        '.aperant/roadmap/<scope>/milestones.json',
        '.aperant/roadmap/<scope>/ROADMAP.md',
        '.aperant/roadmap/**',
        '.aperant/events/**',
        '.aperant/team-status/**',
        '.aperant/state.json',
        // C17 — declarative YAML workflow runs emit artifacts under
        // .aperant/wfrun/{run-id}/ (manifest.json, plan.json, result.json).
        // The `<id>` placeholder used for tasks doesn't apply here — run-ids
        // are orthogonal, so the allowlist uses a literal wildcard.
        '.aperant/wfrun/**',
        // C27 — apt-tools design-scan writes tokens.json plus future design
        // artifacts under .aperant/design/. Shared across tasks (a single
        // design-scan run feeds every downstream apt:design task), so the
        // allowlist is a literal wildcard rather than task-scoped.
        '.aperant/design/**',
    ]),
});
/**
 * Resolve the canonical outputs for a new task based on its linkage.
 *
 * @internal
 */
export function resolveDefaultOutputs(task) {
    const linked = Boolean(task?.milestone_id && task?.phase_id);
    const template = linked ? DEFAULT_TASK_OUTPUTS['phase-linked'] : DEFAULT_TASK_OUTPUTS.unlinked;
    // Expand `<id>`, `<scope>`, `<phase-id>` tokens so the stored allowlist is
    // self-contained (no re-expansion at check time).
    const id = task?.id || task?.task_id || '';
    const scope = task?.scope || 'project';
    const phaseId = task?.phase_id || '';
    return template.map((pat) => pat
        .replace(/<id>/g, id)
        .replace(/<scope>/g, scope)
        .replace(/<phase-id>/g, phaseId));
}
/**
 * Minimal glob matcher. Supports:
 *   - `**` matches any sequence of characters including `/`
 *   - `*`  matches any sequence of non-`/` characters
 *   - literal `.` and other path characters escaped safely
 *
 * Intentionally does NOT support negation, character classes, or braces —
 * the outputs templates above are the only consumers, and they never need
 * those features.
 *
 * @internal
 */
export function globToRegex(pat) {
    let re = '';
    for (let i = 0; i < pat.length; i++) {
        const c = pat[i];
        if (c === '*' && pat[i + 1] === '*') {
            re += '.*';
            i++;
        }
        else if (c === '*') {
            re += '[^/]*';
        }
        else if ('+.()|^$?{}[]\\'.includes(c)) {
            re += `\\${c}`;
        }
        else if (c === '/') {
            re += '/';
        }
        else {
            re += c;
        }
    }
    return new RegExp(`^${re}$`);
}
/**
 * Check whether `path` is allowed by any glob in `patterns`.
 *
 * @internal
 */
export function matchesAny(path, patterns) {
    for (const pat of patterns) {
        if (!pat)
            continue;
        if (globToRegex(pat).test(path))
            return true;
    }
    return false;
}
/**
 * Validate a list of paths against the task's declared outputs. Returns an
 * array of offending paths (empty if everything is allowed).
 *
 * @internal
 */
export function findOutputsViolations(outputs, paths) {
    // Treat a missing or empty outputs list as "reject everything" — the
    // sentinel originally exercised by C29's smoke-drain harness (retired in
    // C30 in favor of src/__tests__/command-coverage.test.ts). Callers that
    // want unrestricted writes pass `['**']` explicitly.
    if (!Array.isArray(outputs))
        return paths.slice();
    if (outputs.length === 0)
        return paths.slice();
    const bad = [];
    for (const p of paths) {
        if (!matchesAny(p, outputs))
            bad.push(p);
    }
    return bad;
}
/**
 * Throw-on-violation variant. Error shape is structured so callers can emit
 * a machine-readable JSON diagnostic.
 *
 * @internal
 */
export function assertWithinOutputs(outputs, paths) {
    const violations = findOutputsViolations(outputs, paths);
    if (violations.length > 0) {
        const err = new Error(`outputs-violation: ${violations.length} path(s) outside task outputs: ${violations.slice(0, 5).join(', ')}`);
        err.code = 'outputs-violation';
        err.violations = violations;
        err.outputs = outputs;
        throw err;
    }
}
//# sourceMappingURL=outputs-guard.mjs.map