/** * Seat STANDING — what the daemon can say about a roster seat's usability from what it already * knows, so the roster and the chat admission read the SAME predicate (F-2R2-009, F-2R2-007). * * # The defect * * `GET /roster` carried `signed_in: false` for four seats and the Health rail rendered every one * of them as "active · signed out" with a green tick. Two of those seats mean two different * things by "signed out": codex / pi / copilot bench on their first ballot (`non_zero_exit`: "No * API key found", "exceeded your monthly quota"), while opencode ANSWERED a scoped chat on its * free tier (OpenCode Zen, no account). One word, two outcomes, and nothing on the wire to tell * them apart — and `POST /chats` seated its defaults through a third, unrelated filter. * * # The model * * - `auth` — the sign-in probe's three values re-read against the seat's free-tier capability: * `signed_in` (a credential artifact is observable), `signed_out` (none is, and the seat needs * one to answer), `not_required` (none is, but the seat answers unauthenticated on a free tier * — see {@link FREE_TIER_SEATS}), `unknown` (the probe cannot tell cheaply: keychain-backed * seats, seat keys with no rule). * - `council_eligible` — whether a council would seat and keep this seat as far as the daemon * can tell: enabled for council, runtime health `active`, and `auth` not `signed_out`. A * PREDICTION from the daemon's own records, not the engine's verdict — the engine convenes * whatever roster it is handed and benches a seat only after it fails (F-4R2-007). `unknown` * auth is eligible: refusing a seat the daemon cannot read would bench working keychain seats. * - `work_eligible` (crew#916, wicked-core#572) — the same gates read for the seat's OTHER role: * `seat_eligible_for_work` not false instead of `enabled_for_council`. The two flags are * independent (a ballot-only seat, a work-only seat); neither alone makes the seat unusable. * - chat admission ({@link chatSeatAdmission}) — the seats a chat seats BY DEFAULT: the same * auth predicate (a `signed_out` seat is refused up front with the reason, instead of failing * its first turn), plus the engine's scope rule restated (a SCOPED chat holds only a seat whose * ACP adapter asks permissions or whose record arms the kernel write floor). Every refusal * carries every reason that applies, so the thread can say WHY a seat is missing. * * Pure, synchronous, no IO: the probe result and the seat's own auth refusal are the inputs. The * runtime HEALTH record is not one any more (R5 / R5b, DES-L3 PR-3D) — `inactive` is never produced * and the one bench is the engine's per-run ballot ledger. */ import type { SeatAuthFailure, SeatRecentBench } from './seat-health.js'; import type { SeatProbeReading } from './seat-probe.js'; /** * crew#771: a seat's own words, when they are one short plain line — what the plain sentence * (`council_ineligible_reason`, a chat refusal) may quote. `null` for anything else: a JSON blob * (claude's `auth status` prints its whole config, two home paths included), a path, several lines * or a long text. Those stay in `auth_evidence`, the technical-details field. */ export declare function plainEvidence(detail: string): string | null; /** * `auth_evidence` with the operator's home directory written `~` (crew#771: screen shares and * recordings). A command's display passes `$HOME` instead (`seat-agents.ts`): it expands inside the * double quotes the engine puts around a seat's root, where `~` does not. Only the home as a whole path segment — `/home/al` never eats the start of * `/home/alice` — and in every separator spelling: `/`, `\`, and the JSON-escaped `\\` a Windows * home takes inside a CLI's JSON status (codex on #797). A root home (`/`) is left alone. */ export declare function homeless(detail: string, home?: string, replacement?: string): string; /** The seat's auth state, read for what it MEANS for the seat's usability. */ export type SeatAuth = 'signed_in' | 'signed_out' | 'not_required' | 'unknown'; /** * Seats known to answer with NO credential at all — a free tier the CLI selects by itself when no * provider is configured. A CREW-SIDE HEURISTIC, keyed by roster `key` (independent review of #533, * F-1): the CLI registry declares no credential requirement today (`AgenticCli` has none; * `AcpConfig.auth_method` is the ACP transport's auth, `None` for every built-in), so the daemon * cannot read this off the record and says so on the wire (`free_tier_source: 'crew-heuristic'`). * The day the `[cli]` record carries `credential = "optional"` (wicked-core follow-up), {@link seatAuth} * reads the record first and reports `free_tier_source: 'registry'` — this table is the fallback. * opencode runs its free "OpenCode Zen" models (`big-pickle` and friends) with no account, which is * exactly what the fresh-rig chat exercised (F-2R2-009). */ export declare const FREE_TIER_SEATS: Readonly>; /** Where a `not_required` reading came from: the CLI's registry record, or crew's own table. */ export type FreeTierSource = 'registry' | 'crew-heuristic'; /** The auth reading for one seat: the probe's answer, re-read against the seat's credential requirement. */ export declare function seatAuth(seat: StandingSeat, signedIn: boolean | null): { auth: SeatAuth; free_tier?: string; free_tier_source?: FreeTierSource; }; /** The roster fields standing reads. Structural, so both the wire seat and a test stub satisfy it. */ export interface StandingSeat { key: string; /** wicked-core#572: may cast a council ballot — and nothing more. */ enabled_for_council?: boolean; /** wicked-core#572: may be seated for work (routing, failover, reassign, the lead pick, the * agent judge). Absent = true. */ seat_eligible_for_work?: boolean; acp?: { acp_input_governance?: boolean; os_sandbox?: boolean; verified_version?: unknown; } | null; /** IG1-core-3: the engine's derived class (`acp_input_governance | os_sandbox | none`). */ governance_class?: unknown; trust_flags?: unknown; /** core#581: the probed ACP version pin, when the seat pins one. */ version_pin?: { matched?: unknown; pinned?: unknown; observed?: unknown; } | null; /** A registry-declared credential requirement, when the engine's record carries one (future core). */ credential?: 'required' | 'optional' | string; /** A registry-declared free-tier label, when the record carries one (future core). */ free_tier?: string; /** wicked-core#976: the engine's host-floor bench reason for this seat (`roster-standing.ts` * stamps it from `CoreAdapter.hostFloorBenched`), absent when the engine does not bench it. */ host_floor_bench?: string | null; /** wicked-core#976: present when the engine's host-floor bench could not be read on the host * floor (and no earlier reading stands): the operator's sentence. The seat is not admitted. */ host_floor_unknown?: string | null; /** crew#1071: the engine's bench of this seat that is still active (`RosterSeat.recent_bench`): * an ask does not seat it until the bench lifts. Absent or `null`: no active bench. */ recent_bench?: { kind?: unknown; lifts_at?: unknown; reason?: unknown; } | null; } /** * The operator's sentence for a seat the engine benches on the host floor (wicked-core#976): the * engine refuses its governed work there, so a job never gives it any. It still reviews, triages * and votes (those calls are not governed), which is why `council_eligible` does not read it. */ export declare const HOST_FLOOR_BENCH_COPY: string; /** The operator's sentence when the host-floor bench could not be read (fail closed). */ export declare const HOST_FLOOR_UNKNOWN_COPY: string; export interface SeatStanding { auth: SeatAuth; /** Present when `auth` is `not_required`: the free tier the seat answers on. */ free_tier?: string; /** Present with `free_tier`: whether the CLI registry declared it, or crew's own table did. */ free_tier_source?: FreeTierSource; /** Where `auth` came from (F-A45-006): `seat-stderr` = the seat ITSELF reported no credential * (a council ballot, a worker failure or the ACP handshake said "No API key found" / 401 …), * which overrides everything else; `probe` (crew#630) = the seat's own auth-status command * answered (`seat-probe.ts`), which overrides the credential-file heuristic; absent = the * file heuristic (`signed_in`) decided. */ auth_source?: 'seat-stderr' | 'probe'; /** Present with `auth_source: 'seat-stderr'`, and with `probe` when it read signed out: the * seat's own words, bounded. */ auth_evidence?: string; /** Present with `auth_source: 'probe'`: ISO-8601 of the probe the reading is from. */ probed_at?: string; /** How far `auth` was VERIFIED (crew#645): `live` — the seat answered an authenticated request, * or refused one for want of a credential (its own words decided); `status` — only the CLI's * status command answered, which reads the stored login and not the session (an expired OAuth * login still reads signed in there); `unverified` — nothing asked the seat (no status command, * or its check has not answered or could not tell). */ login_check: LoginCheck; /** Present unless `login_check` is `live`: why the login is not verified, in the operator's words * (it starts "login unverified"). */ login_note?: string; council_eligible: boolean; /** Present when `council_eligible` is false: the one reason, in the operator's words. */ council_ineligible_reason?: string; /** crew#916: whether the seat may be seated for WORK as far as the daemon can tell — * `seat_eligible_for_work` not false, under the same auth / bench / login gates as * `council_eligible`. */ work_eligible: boolean; /** Present when `work_eligible` is false: the one reason, in the operator's words. */ work_ineligible_reason?: string; } /** The cause a "no credential" sentence names: `: ` when they are plain, else the source alone (crew#771). */ export declare function noCredentialCause(failure: Pick): string; /** See {@link SeatStanding.login_check}. */ export type LoginCheck = 'live' | 'status' | 'unverified'; /** The probe input: its answer, `'pending'` for a probed seat whose check has not answered yet, * `undefined` for a seat with no probe. */ export type ProbeInput = SeatProbeReading | 'pending' | undefined; /** A seat whose `auth` lets it take a turn — the ONE predicate the roster and the chat share. */ export declare function authUsable(auth: SeatAuth): boolean; export declare function seatStanding(seat: StandingSeat, signedIn: boolean | null, authFailure?: SeatAuthFailure | null, probeInput?: ProbeInput, recentBench?: SeatRecentBench | null): SeatStanding; /** * The reason a seat can take NO role right now, whatever its role flags say (signed out, an active * engine bench, a login check still running), or `null` — the gates `council_eligible` and * `work_eligible` share (crew#916). */ /** * The operator's sentence for a seat the engine benched in a recent run (`RosterSeat.benchReason`, * and `council_ineligible_reason` while it stands). It leads with the engine's cause token, so the * engine's `degradedReason` names it (`copilot (recent quota_exhausted — launcher)`). */ export declare function recentBenchReason(recentBench: SeatRecentBench): string; /** Why a seat was not seated (F-A45-011; `ChatSeatRefusal.source` on the wire): `auth` — signed * out; `scope` — the scoped-chat admission rule; * `budget` — the engine did not seat it (its warm-up timed out or it was dropped at dispatch; R5b — * crew keeps no council bench of its own, so the engine's per-run bench surfaces here); * `engine` — the engine refused it with its own reason. */ export type ChatRefusalSource = 'auth' | 'scope' | 'budget' | 'engine'; export type ChatAdmission = { ok: true; } | { ok: false; reason: string; source: ChatRefusalSource; }; /** * Whether a chat seats this seat BY DEFAULT — the seat's own auth standing plus, for a SCOPED chat, * the engine's admission rule (`acp_runner.rs` `scoped_seat_admission`) restated so a refused seat * is named with its reason instead of silently absent (F-2R2-007). Explicitly requested seats * (`clis`) bypass this: the engine refuses them per seat, and its reason rides on the outcome. * Takes the seat's `auth` only (independent review of #533, F-4): a chat is not a council, so * `council_eligible` / health are deliberately NOT consulted here — a seat benched in councils can * still answer a chat, and saying otherwise would refuse a working seat. */ export declare function chatSeatAdmission(seat: StandingSeat, auth: SeatAuth, scoped: boolean, /** `pool`: the warm chat pool (an ACP session held read-only — the structural rules below). * `path` (ASK-C1/C2, DES-ASK-TEAM-CHAT-001 §4.2): an ask is a team RUN. The seat's standing * (signed in, enabled) is the gate; the structural rule stays ONLY where the run itself cannot * hold the repositories read-only — a scoped ask that is not bound to one repository (several * repos, a project, everything): no worktree, no guard, no default sandbox (codex on #810 r9). * `path-bound`: a single-repo ask — the run is bound (worktree snapshot + guard + mutation * check), so every seat in standing is eligible, wrapped ones included: every seat has at * least the repository boundary there (IG1-crew-2; `governance_mode.scoped_bound` names it). */ kind?: 'pool' | 'path' | 'path-bound'): ChatAdmission; //# sourceMappingURL=seat-standing.d.ts.map