/** * Seat-scoped negotiation protocol rules (v2 client-advocate protocol). * * v2 fixes exactly one initiating seat per match (`metadata.initiatorUserId`, * stamped at discovery time — IND-396) and makes consent asymmetric: * **accept can only come from the counterparty seat**, schema-enforced. * * Vocabulary per seat (v2): * - initiator: `outreach | counter | question | withdraw` (no accept) * - counterparty: `accept | decline | counter | question` * - final turn: initiator `withdraw | counter`; counterparty `accept | decline` * * v1 tasks keep the legacy vocabulary (`propose | accept | reject | counter | * question`) — the version is inherited per conversation, never re-stamped, so * in-flight v1 negotiations are grandfathered untouched. * * Outcome mapping is version-independent: `accept` → opportunity `pending`, * `reject`/`withdraw`/`decline` → `rejected`, turn-cap → `stalled`. */ import { z } from "zod"; import type { NegotiationAction, NegotiationSeat, NegotiationProtocolVersion } from "../../shared/schemas/negotiation-state.schema.js"; /** Initiator seat, non-final turn: may reach out, push back, ask, or walk away — never accept. */ export declare const InitiatorTurnSchema: z.ZodObject<{ action: z.ZodEnum<["outreach", "counter", "question", "withdraw"]>; assessment: z.ZodObject<{ reasoning: z.ZodString; suggestedRoles: z.ZodObject<{ ownUser: z.ZodEnum<["agent", "patient", "peer"]>; otherUser: z.ZodEnum<["agent", "patient", "peer"]>; }, "strip", z.ZodTypeAny, { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }, { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }>; }, "strip", z.ZodTypeAny, { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }, { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }>; message: z.ZodOptional>; /** Present when action is `ask_user` (v2, P3.2). */ askUser: z.ZodOptional; }, "strict", z.ZodTypeAny, { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; }, { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; }>>>; }, "strip", z.ZodTypeAny, { action: "counter" | "question" | "outreach" | "withdraw"; assessment: { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }; message?: string | null | undefined; askUser?: { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; } | null | undefined; }, { action: "counter" | "question" | "outreach" | "withdraw"; assessment: { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }; message?: string | null | undefined; askUser?: { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; } | null | undefined; }>; /** Counterparty seat, non-final turn: the only seat that can accept. */ export declare const CounterpartyTurnSchema: z.ZodObject<{ action: z.ZodEnum<["accept", "decline", "counter", "question"]>; assessment: z.ZodObject<{ reasoning: z.ZodString; suggestedRoles: z.ZodObject<{ ownUser: z.ZodEnum<["agent", "patient", "peer"]>; otherUser: z.ZodEnum<["agent", "patient", "peer"]>; }, "strip", z.ZodTypeAny, { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }, { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }>; }, "strip", z.ZodTypeAny, { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }, { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }>; message: z.ZodOptional>; /** Present when action is `ask_user` (v2, P3.2). */ askUser: z.ZodOptional; }, "strict", z.ZodTypeAny, { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; }, { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; }>>>; }, "strip", z.ZodTypeAny, { action: "accept" | "counter" | "question" | "decline"; assessment: { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }; message?: string | null | undefined; askUser?: { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; } | null | undefined; }, { action: "accept" | "counter" | "question" | "decline"; assessment: { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }; message?: string | null | undefined; askUser?: { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; } | null | undefined; }>; /** Initiator seat, final allowed turn: commit to walking away or leave the door open. */ export declare const FinalInitiatorTurnSchema: z.ZodObject<{ action: z.ZodEnum<["withdraw", "counter"]>; assessment: z.ZodObject<{ reasoning: z.ZodString; suggestedRoles: z.ZodObject<{ ownUser: z.ZodEnum<["agent", "patient", "peer"]>; otherUser: z.ZodEnum<["agent", "patient", "peer"]>; }, "strip", z.ZodTypeAny, { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }, { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }>; }, "strip", z.ZodTypeAny, { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }, { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }>; message: z.ZodOptional>; /** Present when action is `ask_user` (v2, P3.2). */ askUser: z.ZodOptional; }, "strict", z.ZodTypeAny, { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; }, { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; }>>>; }, "strip", z.ZodTypeAny, { action: "counter" | "withdraw"; assessment: { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }; message?: string | null | undefined; askUser?: { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; } | null | undefined; }, { action: "counter" | "withdraw"; assessment: { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }; message?: string | null | undefined; askUser?: { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; } | null | undefined; }>; /** Counterparty seat, final allowed turn: must decide. */ export declare const FinalCounterpartyTurnSchema: z.ZodObject<{ action: z.ZodEnum<["accept", "decline"]>; assessment: z.ZodObject<{ reasoning: z.ZodString; suggestedRoles: z.ZodObject<{ ownUser: z.ZodEnum<["agent", "patient", "peer"]>; otherUser: z.ZodEnum<["agent", "patient", "peer"]>; }, "strip", z.ZodTypeAny, { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }, { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }>; }, "strip", z.ZodTypeAny, { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }, { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }>; message: z.ZodOptional>; /** Present when action is `ask_user` (v2, P3.2). */ askUser: z.ZodOptional; }, "strict", z.ZodTypeAny, { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; }, { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; }>>>; }, "strip", z.ZodTypeAny, { action: "accept" | "decline"; assessment: { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }; message?: string | null | undefined; askUser?: { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; } | null | undefined; }, { action: "accept" | "decline"; assessment: { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }; message?: string | null | undefined; askUser?: { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; } | null | undefined; }>; /** Initiator seat, non-final turn, with the client-consult pause available. */ export declare const InitiatorAskUserTurnSchema: z.ZodObject<{ action: z.ZodEnum<["outreach", "counter", "question", "withdraw", "ask_user"]>; assessment: z.ZodObject<{ reasoning: z.ZodString; suggestedRoles: z.ZodObject<{ ownUser: z.ZodEnum<["agent", "patient", "peer"]>; otherUser: z.ZodEnum<["agent", "patient", "peer"]>; }, "strip", z.ZodTypeAny, { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }, { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }>; }, "strip", z.ZodTypeAny, { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }, { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }>; message: z.ZodOptional>; /** Present when action is `ask_user` (v2, P3.2). */ askUser: z.ZodOptional; }, "strict", z.ZodTypeAny, { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; }, { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; }>>>; }, "strip", z.ZodTypeAny, { action: "counter" | "question" | "outreach" | "withdraw" | "ask_user"; assessment: { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }; message?: string | null | undefined; askUser?: { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; } | null | undefined; }, { action: "counter" | "question" | "outreach" | "withdraw" | "ask_user"; assessment: { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }; message?: string | null | undefined; askUser?: { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; } | null | undefined; }>; /** Counterparty seat, non-final turn, with the client-consult pause available. */ export declare const CounterpartyAskUserTurnSchema: z.ZodObject<{ action: z.ZodEnum<["accept", "decline", "counter", "question", "ask_user"]>; assessment: z.ZodObject<{ reasoning: z.ZodString; suggestedRoles: z.ZodObject<{ ownUser: z.ZodEnum<["agent", "patient", "peer"]>; otherUser: z.ZodEnum<["agent", "patient", "peer"]>; }, "strip", z.ZodTypeAny, { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }, { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }>; }, "strip", z.ZodTypeAny, { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }, { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }>; message: z.ZodOptional>; /** Present when action is `ask_user` (v2, P3.2). */ askUser: z.ZodOptional; }, "strict", z.ZodTypeAny, { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; }, { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; }>>>; }, "strip", z.ZodTypeAny, { action: "accept" | "counter" | "question" | "decline" | "ask_user"; assessment: { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }; message?: string | null | undefined; askUser?: { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; } | null | undefined; }, { action: "accept" | "counter" | "question" | "decline" | "ask_user"; assessment: { reasoning: string; suggestedRoles: { ownUser: "agent" | "patient" | "peer"; otherUser: "agent" | "patient" | "peer"; }; }; message?: string | null | undefined; askUser?: { reason: "unresolved_owner_constraint" | "consequential_disclosure_permission" | "repeated_non_convergence" | "insufficient_commitment_authority"; } | null | undefined; }>; /** * The set of actions a given seat may submit under a given protocol version. * * v1 ignores the seat entirely (legacy symmetric vocabulary) so pre-v2 * negotiations behave exactly as before. */ export declare function allowedActionsFor(version: NegotiationProtocolVersion, seat: NegotiationSeat, isFinalTurn?: boolean, opts?: AskUserOpts): readonly NegotiationAction[]; /** * Zod turn schema for a system-agent turn, selected by version + seat + * final-turn flag. v1 returns the legacy schemas (seat-agnostic); v2 returns * the seat-scoped schemas above, making an initiator `accept` structurally * impossible rather than prompt-discouraged. * * The v1 legacy schemas are passed in by the caller (they live in * `negotiation.state.ts`) to keep this module free of a state-module import. */ export declare function turnSchemaFor(version: NegotiationProtocolVersion, seat: NegotiationSeat, isFinalTurn: boolean, v1Schemas: { system: z.ZodTypeAny; final: z.ZodTypeAny; }, opts?: AskUserOpts): z.ZodTypeAny; /** * Opt-in extension of the seat vocabulary with the `ask_user` client-consult * pause (P3.2). Never granted on final-cap turns (the final turn must decide) * and never under v1. Callers pass `{ askUser: true }` only when the full * pause loop is available on their surface: the ask-user feature flag is on, * a questioner enqueue and an answer-window timer are wired, the negotiation * has an opportunity to resume against, and the acting side has not already * consumed its one client question for this negotiation (rationing). */ export interface AskUserOpts { askUser?: boolean; } /** Terminal actions end the negotiation immediately. */ export declare function isTerminalAction(action: string | undefined | null): boolean; /** Reject-like actions map the opportunity to `rejected` (v1 reject, v2 withdraw/decline). */ export declare function isRejectLikeAction(action: string | undefined | null): boolean; /** * Conservative action when an agent produced schema-invalid output (after the * retry) or an internal error needs a seat-valid terminal placeholder. * * Non-final turns fall back to `counter` (keeps the dialogue open — the AC's * "conservative counter"). Final turns must decide: v1 → `reject`, v2 * counterparty → `decline`, v2 initiator → `counter` is still legal on the * final turn so it stays `counter` (finalizes as turn-cap/stalled). */ export declare function fallbackActionFor(version: NegotiationProtocolVersion, seat: NegotiationSeat, isFinalTurn: boolean): NegotiationAction; /** Seat-appropriate reject-like action for error paths. */ export declare function rejectActionFor(version: NegotiationProtocolVersion, seat: NegotiationSeat): NegotiationAction; /** * Read the protocol version off task metadata. Returns null when the task * predates version stamping (treat as v1 at the call site when the task is a * genuine prior; fresh tasks stamp from {@link configuredProtocolVersion}). */ export declare function readProtocolVersion(metadata: { protocolVersion?: unknown; } | null | undefined): NegotiationProtocolVersion | null; /** * Protocol version for negotiations without a prior task for the same * opportunity, from the `NEGOTIATION_PROTOCOL_VERSION` env switch. Defaults * to `v1` when unset — v2 is opt-in per environment, and rolling back is the * same single switch (only in-flight negotiations stay pinned to their * stamped version). */ export declare function configuredProtocolVersion(): NegotiationProtocolVersion; /** * Whether the `ask_user` client-consult pause (P3.2) is enabled, from the * `NEGOTIATION_ASK_USER_ENABLED` env switch. Defaults to off — the deployment * is byte-for-byte unchanged until the flag is flipped, and rolling back is * the same single switch. */ export declare function configuredAskUserEnabled(): boolean; /** Default answer window for a paused `ask_user` negotiation: 24 hours. */ export declare const DEFAULT_ASK_USER_WINDOW_MS: number; /** * Answer window for a paused `ask_user` negotiation, in ms. Overridable via * `NEGOTIATION_ASK_USER_WINDOW_MS` (dev/e2e use shorter windows to exercise * the expiry path); invalid or non-positive values fall back to 24 h. */ export declare function askUserAnswerWindowMs(): number; /** * Slack added on top of the answer window when deciding whether a paused * (`input_required`) task still holds the conversation lock. Covers expiry * worker delay: the lock must outlive the timer slightly, so ambient * rediscovery cannot slip in between window expiry and the worker's resume. */ export declare const ASK_USER_LOCK_SLACK_MS: number; /** * Resolve the seat of `userId` on a negotiation task. * * Keys on `metadata.initiatorUserId` (the rigid v2 stamp), **never on turn * parity** — continuations can start with either side speaking first, so * parity misattributes seats across sessions. Pre-stamp tasks fall back to * `sourceUserId` (the discovery-session opener), which is what the stamp * defaults to anyway. */ export declare function resolveSeat(userId: string, metadata: { initiatorUserId?: unknown; sourceUserId?: unknown; } | null | undefined): NegotiationSeat; /** Human-readable seat-violation message shared by respond surfaces. */ export declare function seatViolationMessage(action: string, seat: NegotiationSeat, version: NegotiationProtocolVersion): string;