/**
 * Race a tone/relevance-gate review against the outbound route's budget.
 *
 * Background — the production failure this exists to prevent (2026-06-08):
 * `MessagingToneGate.review` is FAIL-OPEN by design, but it will wait up to
 * RATE_LIMIT_WAIT_MS (120s) for a rate-limit window PLUS the call itself, all
 * inside a single `await`. Under sustained rate-limit pressure the gate
 * routinely finished at 121s–185s (observed in the tone-gate decision log, all
 * failedOpen) — past the 120s outbound route budget. The route then 408s, which
 * is the WORST outcome: the message both bypasses the gate AND, because the send
 * "failed", the calling session falls back to dumping the note into whatever
 * topic it is active in (the "patch notes landing in a working topic" bug).
 *
 * This helper makes the fail-open a STRUCTURAL guarantee at the route seam: it
 * resolves to the real verdict if the gate answers within `budgetMs`, otherwise
 * to a `budgetExceeded` fail-open result (pass=true) so the route delivers the
 * message rather than holding it past the route budget. The behaviour is
 * independent of provider internals, so a future provider/rate-limit change can
 * never re-introduce the hang. Same contract as the ArcCheck 200ms race that
 * already guards the signal-collection phase.
 *
 * Pure + injectable (`budgetMs`, `now`, `schedule`) so the budget semantics are
 * unit-tested deterministically without a real 20s wait.
 *
 * @param reviewPromise the in-flight `MessagingToneGate.review(...)` promise
 * @param budgetMs       hard budget; pass OUTBOUND_GATE_REVIEW_BUDGET_MS
 * @param now            clock injection (defaults to Date.now)
 * @param schedule       timer injection (defaults to setTimeout) — lets tests
 *                       fire the budget deterministically
 */
export async function reviewWithinBudget(reviewPromise, budgetMs, now = Date.now, schedule = (cb, ms) => {
    // unref so a pending budget timer never keeps the process alive
    const t = setTimeout(cb, ms);
    if (typeof t.unref === 'function') {
        t.unref();
    }
}) {
    const start = now();
    const BUDGET_EXCEEDED = Symbol('outbound-gate-budget-exceeded');
    const outcome = await Promise.race([
        reviewPromise,
        new Promise((resolve) => schedule(() => resolve(BUDGET_EXCEEDED), budgetMs)),
    ]);
    if (outcome === BUDGET_EXCEEDED) {
        return {
            pass: true,
            rule: '',
            issue: '',
            suggestion: '',
            latencyMs: now() - start,
            failedOpen: true,
            budgetExceeded: true,
        };
    }
    return outcome;
}
//# sourceMappingURL=outboundGateBudget.js.map