import * as node_sqlite from 'node:sqlite';
import { JWTPayload } from 'jose';
import { Role, Permission, Member, Teammate, Team, BriefingResponse, ActivityEvent, ActivityRow, ObjectiveStatus, Objective, ObjectiveEvent, CreateObjectiveRequest, UpdateObjectiveRequest, CompleteObjectiveRequest, CancelObjectiveRequest, ReassignObjectiveRequest, UpdateWatchersRequest, Attachment, PermissionPresets, TokenOrigin, TokenInfo } from '@agentc7/sdk/types';
import { z } from 'zod';
import { ActivityStore as ActivityStore$1, ListActivityFilter as ListActivityFilter$1, ActivityListener } from '@agentc7/core';

type NodeSqliteModule = typeof node_sqlite;
type DatabaseSyncInstance = InstanceType<NodeSqliteModule['DatabaseSync']>;
/**
 * Open the ac7 server database at `path` and apply the PRAGMAs
 * every module expects. Pass `:memory:` for an in-memory DB (tests,
 * ephemeral runs). The returned handle is owned by the caller —
 * typically `runServer`, which closes it during shutdown.
 */
declare function openDatabase(path: string): DatabaseSyncInstance;

/**
 * On-disk persistence for TLS certs.
 *
 * ac7 stores the active server cert + key next to its config
 * file at `<configDir>/certs/server.{crt,key}`. The files are written
 * at 0o600 via the same atomic-write helper the config file uses, so
 * a fresh generation is safe against crashes mid-write and never
 * leaks a readable key.
 *
 * Modes:
 *   - `self-signed`: load an existing cert if it's valid and not near
 *     expiry; otherwise generate a fresh one and persist it.
 *   - `custom`: load user-supplied cert/key from configured paths.
 *     No regeneration — the user manages renewal.
 *   - `off`: never called.
 */
declare class HttpsConfigError extends Error {
    constructor(message: string);
}
interface LoadedCert {
    cert: string;
    key: string;
    /** Path on disk (absolute) — null for ad-hoc generation with no persistence. */
    certPath: string | null;
    keyPath: string | null;
    /** UNIX ms notAfter, or null if indeterminate (custom cert failed to parse). */
    expiresAt: number | null;
    /** Short label for logs. */
    source: 'self-signed:fresh' | 'self-signed:reused' | 'custom';
}

/**
 * JWKS-backed JWT verification for team-scoped access tokens.
 *
 * ac7 is issuer-agnostic: we trust any issuer that publishes an
 * RS256 JWKS at a URL the operator pins in config. This middleware
 * doesn't know or care which identity provider stamped the token —
 * it just verifies the signature and required claims.
 *
 * Scope: verification only. Minting tokens is the issuer's job; the
 * ac7 server never holds a signing key.
 *
 * Claim contract:
 *   - iss      — matches config.issuer
 *   - aud      — matches config.audience (typically `team:<team-id>`)
 *   - sub      — opaque account identifier (logged for audit; no
 *                local meaning)
 *   - member   — the member name inside this team; must exist in the
 *                roster or the request is rejected
 *   - role     — informational; authorization still runs off the
 *                resolved LoadedMember's permission grants
 *   - iat/exp/nbf/jti — standard JWT metadata
 *
 * Why name-based `member` instead of a stable id: tokens are
 * short-lived, so the rename-invalidates-outstanding-tokens window
 * is a non-issue in practice. Switching to a stable id would add a
 * mapping layer on both sides for no user-visible win.
 */

interface JwtConfig$1 {
    /** Expected `iss` claim — the issuer URL the operator trusts. */
    issuer: string;
    /** Expected `aud` claim — typically `team:<this-team-id>`. */
    audience: string;
    /** Absolute URL of the issuer's JWKS document. */
    jwksUrl: string;
}
interface JwtVerifier {
    verify(token: string): Promise<VerifiedClaims>;
}
interface VerifiedClaims {
    /** The `member` claim — resolves to a LoadedMember by name. */
    member: string;
    /** The `sub` claim — opaque account identifier, logged for audit. */
    subject: string;
    /** Raw payload, for callers that want the whole object. */
    payload: JWTPayload;
}
/**
 * Build a verifier against a remote JWKS. The JWKS is fetched lazily
 * on first use and cached internally by `jose` (keyed on `kid`). A
 * signing key rotation on the issuer side is picked up automatically
 * — `jose` refetches when it sees a kid it doesn't know.
 *
 * Throws on verification failure (bad signature, wrong iss/aud,
 * expired, not-yet-valid). The caller is expected to return 401.
 */
declare function createJwtVerifier(config: JwtConfig$1): JwtVerifier;
declare function looksLikeJwt(raw: string): boolean;

/**
 * Minimal structured logger for the ac7 server.
 *
 * Writes one JSON line per event to stderr so it can be piped straight
 * into journald / a log shipper. Stdout is left clean for the server's
 * startup banner.
 */
interface LogContext {
    [key: string]: unknown;
}
interface Logger {
    debug(msg: string, ctx?: LogContext): void;
    info(msg: string, ctx?: LogContext): void;
    warn(msg: string, ctx?: LogContext): void;
    error(msg: string, ctx?: LogContext): void;
}

/**
 * Team config loading for the ac7 server.
 *
 * A team config defines the directive, the permission presets, and
 * the members that make up the team. Each member carries a name, a
 * role (title + description), per-member permissions (preset name or
 * leaf), personal instructions, and a hashed bearer token. Humans vs
 * agents is not a first-class distinction — members are just
 * members, and TOTP enrollment is optional for anyone.
 *
 * On disk the config stores SHA-256 hashes, not plaintext secrets.
 * Humans editing the file by hand can paste a plaintext `token`; the
 * server will hash it on next boot and rewrite the file. A broker
 * compromise via read-only disk access therefore leaks hashes, not
 * the original tokens.
 *
 * Config file format (JSON):
 *
 *   {
 *     "_comment": "...",
 *     "team": {
 *       "name": "demo-team",
 *       "directive": "Ship the payment service.",
 *       "context": "We own the full lifecycle...",
 *       "permissionPresets": {
 *         "admin":    ["team.manage", "members.manage", "objectives.create", "objectives.cancel", "objectives.reassign", "objectives.watch", "activity.read"],
 *         "operator": ["objectives.create", "objectives.cancel", "objectives.reassign"]
 *       }
 *     },
 *     "members": [
 *       { "name": "director-1",  "role": { "title": "director", "description": "Leads the team." },
 *         "instructions": "Approve objectives before they go to the team.",
 *         "permissions": ["admin"],
 *         "tokenHash": "sha256:..." },
 *       { "name": "engineer-1", "role": { "title": "engineer", "description": "Ships code." },
 *         "instructions": "", "permissions": [],
 *         "token": "ac7_plaintext_for_migration" }
 *     ]
 *   }
 *
 * `permissions` entries may be preset names (resolved via the team's
 * `permissionPresets`) or leaf permission strings; the server
 * validates each entry resolves.
 */

/**
 * Set the process-wide KEK. Call once during server startup from
 * `runServer`. Passing `null` explicitly disables encryption.
 */
declare function setKek(kek: Buffer | null): void;
/** Hash a raw bearer token into the on-disk representation. */
declare function hashToken(rawToken: string): string;
/**
 * A member materialized in memory once hashes are known. Extends the
 * wire `Member` with server-only fields — TOTP enrollment and replay
 * guard state, plus the raw (unresolved) permissions list so we can
 * round-trip preset references to disk without expanding them.
 */
interface LoadedMember extends Member {
    /** Preset names + leaf permissions as written on disk; preserved for round-tripping. */
    rawPermissions: string[];
    totpSecret?: string | null;
    totpLastCounter?: number;
}
declare const WebPushConfigSchema: z.ZodObject<{
    vapidPublicKey: z.ZodString;
    vapidPrivateKey: z.ZodString;
    vapidSubject: z.ZodDefault<z.ZodString>;
}, z.core.$strip>;
declare const HttpsConfigSchema: z.ZodObject<{
    mode: z.ZodDefault<z.ZodEnum<{
        off: "off";
        "self-signed": "self-signed";
        custom: "custom";
    }>>;
    bindHttp: z.ZodDefault<z.ZodNumber>;
    bindHttps: z.ZodDefault<z.ZodNumber>;
    redirectHttpToHttps: z.ZodDefault<z.ZodBoolean>;
    hsts: z.ZodDefault<z.ZodEnum<{
        off: "off";
        auto: "auto";
        on: "on";
    }>>;
    selfSigned: z.ZodDefault<z.ZodObject<{
        lanIp: z.ZodDefault<z.ZodNullable<z.ZodString>>;
        validityDays: z.ZodDefault<z.ZodNumber>;
        regenerateIfExpiringWithin: z.ZodDefault<z.ZodNumber>;
    }, z.core.$strip>>;
    custom: z.ZodDefault<z.ZodObject<{
        certPath: z.ZodDefault<z.ZodNullable<z.ZodString>>;
        keyPath: z.ZodDefault<z.ZodNullable<z.ZodString>>;
    }, z.core.$strip>>;
}, z.core.$strip>;
declare const FilesConfigSchema: z.ZodObject<{
    root: z.ZodOptional<z.ZodString>;
    maxFileSize: z.ZodOptional<z.ZodNumber>;
}, z.core.$strip>;
/**
 * Federated JWT config. When present, the auth middleware verifies
 * bearer tokens with JWT structure against the issuer's JWKS and
 * resolves the `member` claim to a LoadedMember by name. Absent →
 * the JWT path stays dormant and only opaque tokens + session
 * cookies work. See `src/jwt.ts` for the claim contract.
 */
declare const JwtConfigSchema: z.ZodObject<{
    issuer: z.ZodString;
    jwksUrl: z.ZodString;
    audience: z.ZodString;
}, z.core.$strip>;
type HttpsConfig = z.infer<typeof HttpsConfigSchema>;
type WebPushConfig = z.infer<typeof WebPushConfigSchema>;
type FilesConfig = z.infer<typeof FilesConfigSchema>;
type JwtConfig = z.infer<typeof JwtConfigSchema>;
declare class MemberLoadError extends Error {
    constructor(message: string);
}
declare class ConfigNotFoundError extends Error {
    readonly path: string;
    constructor(path: string);
}
/**
 * Expand a raw permissions list (preset names + leaves) against the
 * team's permission presets into a flat, deduplicated array of leaf
 * permissions. Unknown names throw `MemberLoadError` with the
 * offending entry called out.
 */
declare function resolvePermissions(raw: readonly string[], presets: Record<string, Permission[]>, context: string): Permission[];
/** Input to `MemberStore.addMember`. */
interface AddMemberInput {
    name: string;
    role: Role;
    instructions: string;
    /** Raw form — preset names or leaf permissions. Resolved by caller. */
    rawPermissions: string[];
    /** Resolved leaf permissions (derived from `rawPermissions` + presets). */
    permissions: Permission[];
    /**
     * Plaintext bearer token for the legacy in-memory store. The
     * DB-backed store does NOT issue tokens — caller composes a separate
     * `TokenStore.insert(...)` after `addMember` returns. Optional so
     * the new path doesn't need to fabricate a placeholder.
     */
    token?: string;
    totpSecret?: string | null;
}
/** Patch for `MemberStore.updateMember` — any subset of fields may be omitted. */
interface UpdateMemberPatch {
    role?: Role;
    instructions?: string;
    rawPermissions?: string[];
    permissions?: Permission[];
}
interface MemberStore {
    findByName(name: string): LoadedMember | null;
    /**
     * Return the on-disk token hash for `name`, or null if the store
     * doesn't track one. Only the in-memory `MapMemberStore` (used by
     * test fixtures) returns a non-null value; the DB-backed store
     * returns null since auth tokens live in the `tokens` table.
     */
    tokenHashOf?(name: string): string | null;
    recordTotpAccept(name: string, counter: number): LoadedMember | null;
    size(): number;
    /** Snapshot of every member in insertion order. */
    members(): LoadedMember[];
    names(): string[];
    /** True iff at least one member has the `members.manage` permission. */
    hasAdmin(): boolean;
    addMember(input: AddMemberInput): LoadedMember;
    removeMember(name: string): void;
    updateMember(name: string, patch: UpdateMemberPatch): LoadedMember;
    /**
     * Replace a member's TOTP secret. Pass `null` to clear the
     * enrollment. Resets `totpLastCounter` to 0.
     */
    setTotpSecret(name: string, secret: string | null): LoadedMember;
}
declare function defaultConfigPath(env?: NodeJS.ProcessEnv, cwd?: string): string;
declare function defaultHttpsConfig(): HttpsConfig;
/**
 * Generate a fresh cryptorandom bearer token in the standard
 * `ac7_<base64url>` format. 32 raw bytes → 43-char payload (~256 bits).
 */
declare function generateMemberToken(): string;
/**
 * Project the loaded members into a teammate list suitable for the
 * roster and briefing responses. Preserves config ordering. Drops
 * the private `instructions` field (teammates don't see each other's
 * personal instructions).
 */
declare function teammatesFromMembers(store: MemberStore): Teammate[];

declare const SERVER_VERSION: string;

/**
 * Team briefing composition.
 *
 * Turns the raw team config + a specific member into a
 * `BriefingResponse` with a pre-composed `instructions` string ready
 * for the MCP link to hand to `new Server({instructions})`.
 *
 * Voice matters: the instructions are written to COMPLEMENT the
 * member's base identity, not overwrite it. "In this team you go by
 * X" and "Your role here: Y" — team context layered on top of
 * whatever the agent already knows about itself.
 *
 * Why instructions carry the *mechanism* but not the *live objective
 * list*: MCP has no refresh hook for the `instructions` string passed
 * to `new Server()` — it's frozen for the session. If we wrote the
 * current open objectives into it at boot, that list would go stale
 * the moment another member assigns new work. The tool descriptions
 * for `objectives_list` + `objectives_complete` ARE refresh-enabled
 * via `tools/list_changed` and carry the live state. Instructions
 * stay stable, tool descriptions stay live — two surfaces, two
 * purposes, no conflict. `openObjectives` is still returned on the
 * response for non-briefing callers (the web UI + the link's initial
 * tool description build), but no longer rendered into the prose.
 */

interface ComposeBriefingInput {
    self: Member;
    team: Team;
    /** Every teammate on the team, including the caller. */
    teammates: Teammate[];
    /**
     * Objectives currently assigned to the caller with status `active`
     * or `blocked`. Returned verbatim on `BriefingResponse.openObjectives`
     * so the link + web UI can seed their initial state without a
     * second round trip. NOT rendered into the instructions string —
     * see file header for the reasoning.
     */
    openObjectives: BriefingResponse['openObjectives'];
}
/**
 * Compose the briefing response for a member. The `instructions`
 * string on the response is the composed prose (member's personal
 * instructions + team context + teammate list). The `BriefingResponse`
 * itself also carries `name`, `role`, `permissions`, raw
 * `instructions` (just the member's own personal directives), and the
 * team + teammate context for programmatic consumers.
 */
declare function composeBriefing(input: ComposeBriefingInput): BriefingResponse;

/**
 * SQLite-backed pending-enrollment store for the device-code flow.
 *
 * RFC 8628 device authorization grant, adapted for ac7's identity
 * model (broker-as-IdP, members instead of OAuth scopes):
 *
 *   1. Operator runs `ac7 connect` on the device; CLI POSTs /enroll.
 *      Server mints (deviceCode, userCode), inserts a pending row,
 *      returns both plus expires_in / interval.
 *   2. CLI displays the userCode + verification URI; polls
 *      /enroll/poll with deviceCode every `interval` seconds.
 *   3. Director, already logged in via TOTP/session, visits the
 *      verification URI, types the userCode, picks bind-or-create
 *      and any role/permission/label fields. Server marks the row
 *      `approved`, mints a token row, KEK-wraps the plaintext into
 *      `issued_token_ct`.
 *   4. CLI's next poll resolves: server decrypts the plaintext,
 *      deletes the row (single-use), returns the token to the device.
 *
 * What's stored:
 *   - sha256(deviceCode) — never the plaintext, so a DB-read leak
 *     can't replay enrollments
 *   - userCode in canonical 8-char Crockford-base32 uppercase form
 *   - source_ip / source_ua at /enroll time (display-only, helps
 *     a director spot an unexpected request)
 *   - label_hint, then the actual label after approval
 *   - approve mode + creation args (JSON) so a `mode='create'`
 *     approval can re-derive the new member at poll-time without
 *     re-asking the director
 *   - issued_token_id (stable handle for revoke later) and
 *     issued_token_ct (KEK-wrapped plaintext for one-shot delivery)
 *
 * Single-connection-per-process: the store does NOT own its
 * DatabaseSync handle; runServer passes the shared handle in.
 */

/**
 * The KEK is just a 32-byte Buffer; aliasing it here keeps the
 * intent explicit at the call site without forcing every consumer
 * to import from `./kek.js`.
 */
type Kek = Buffer;
/**
 * Enrollment-flow TTL. RFC 8628 §3.2 says expires_in is "RECOMMENDED"
 * — picked 5 min to match the GitHub `gh auth login` window, which
 * operators are already used to. The window covers the full flow:
 * mint → director approve → device poll once after approval. 5 min
 * is plenty for a director who's already in the broker UI.
 */
declare const ENROLLMENT_TTL_MS: number;
/**
 * Default poll interval the server reports back. RFC 8628 §3.5
 * says default 5; clients respect this and add 5 on slow_down.
 */
declare const DEFAULT_POLL_INTERVAL_S = 5;
type EnrollmentStatus = 'pending' | 'approved' | 'rejected';
/**
 * Internal row shape — exposed via the per-method accessors below
 * but never sent on the wire as-is (the wire shapes live in
 * `@agentc7/sdk/types`).
 */
interface EnrollmentRow {
    deviceCodeHash: string;
    userCode: string;
    createdAt: number;
    expiresAt: number;
    status: EnrollmentStatus;
    sourceIp: string | null;
    sourceUa: string | null;
    labelHint: string;
    approvedBy: string | null;
    boundMember: string | null;
    approveArgsJson: string | null;
    issuedTokenId: string | null;
    issuedTokenCiphertext: string | null;
    rejectReason: string | null;
    lastPolledAt: number | null;
}
interface MintInput {
    sourceIp: string | null;
    sourceUa: string | null;
    labelHint?: string;
}
interface MintResult {
    /** Plaintext to return to the CLI; treated as a shared secret. */
    deviceCode: string;
    /** 8-char Crockford base32, formatted for display as `XXXX-XXXX`. */
    userCode: string;
    /** Display form. Same chars as `userCode` but with a hyphen at the midpoint. */
    userCodeFormatted: string;
    /** Internal: the hash actually stored in the DB. */
    deviceCodeHash: string;
    expiresAt: number;
    /** RFC 8628 `expires_in` in seconds. */
    expiresIn: number;
    /** RFC 8628 `interval` in seconds. */
    interval: number;
}
interface ApproveInput {
    userCode: string;
    approvedBy: string;
    boundMember: string;
    /** Stored verbatim so the poll-time handler can apply mode='create' if needed. */
    approveArgsJson: string;
    /** UUID of the token row issued to back this approval. */
    issuedTokenId: string;
    /** Plaintext bearer token to deliver on next poll. KEK-wrapped at rest. */
    issuedTokenPlaintext: string;
}
interface RejectInput {
    userCode: string;
    rejectedBy: string;
    reason?: string;
}
/**
 * RFC 8628-shaped poll outcomes. `approved` carries the plaintext
 * once; the row is consumed in the same transaction so a replay
 * always returns `expired_token`.
 */
type PollOutcome = {
    kind: 'authorization_pending';
} | {
    kind: 'slow_down';
} | {
    kind: 'expired_token';
} | {
    kind: 'access_denied';
    reason: string | null;
} | {
    kind: 'approved';
    tokenPlaintext: string;
    tokenId: string;
    memberName: string;
};
/**
 * Pre-flight check on a user code before approval — used by the
 * approval handler to validate the row is still actionable. Lets
 * us return crisp errors ("already approved", "expired", "not
 * found") rather than a generic 400.
 */
type LookupOutcome = {
    kind: 'pending';
    row: EnrollmentRow;
} | {
    kind: 'expired';
} | {
    kind: 'already_approved';
} | {
    kind: 'already_rejected';
    reason: string | null;
} | {
    kind: 'not_found';
};
/**
 * Normalize a user-code input to the canonical 8-char Crockford
 * uppercase form used for storage and lookup. Accepts any case,
 * any spacing/hyphenation. Returns null on invalid alphabet or
 * length, in which case the caller emits a 400 (not a "not found"
 * — that would leak whether a similar code exists).
 */
declare function normalizeUserCode(input: string): string | null;
/** Format an internal user code (`XXXXYYYY`) as the displayed `XXXX-YYYY`. */
declare function formatUserCode(internal: string): string;
interface EnrollmentStoreOptions {
    /** Test clock injection. */
    now?: () => number;
    /**
     * Active KEK for wrapping the issued plaintext. Pass `null` (or
     * omit) only when the host has no KEK configured — the plaintext
     * is then stored as-is. The migration path should ensure a KEK
     * exists before this store handles real traffic.
     */
    kek?: Kek | null;
}
declare class EnrollmentStore {
    private readonly db;
    private readonly insertStmt;
    private readonly findByCodeStmt;
    private readonly findByDeviceHashStmt;
    private readonly approveStmt;
    private readonly rejectStmt;
    private readonly deleteByDeviceHashStmt;
    private readonly listPendingStmt;
    private readonly touchPollStmt;
    private readonly purgeStmt;
    private readonly now;
    private readonly kek;
    constructor(db: DatabaseSyncInstance, options?: EnrollmentStoreOptions);
    /**
     * Mint a fresh enrollment row. Generates `(deviceCode, userCode)`,
     * inserts the row keyed by `sha256(deviceCode)`, returns the
     * plaintext device code (for the CLI to keep) plus both forms of
     * the user code.
     *
     * Retries a small number of times on user-code collision (32^8
     * space + 5min TTL → collisions are infinitesimally rare in
     * practice, but the retry path keeps boot-time test failures from
     * flaking on absurdly unlucky seed values).
     */
    mint(input: MintInput): MintResult;
    /**
     * Look up by user code (normalized form, no hyphen). Returns a
     * `LookupOutcome` discriminated on whether the row is actionable.
     */
    lookupByUserCode(userCode: string): LookupOutcome;
    /**
     * Mark a pending row approved and stash the issued token for the
     * device-side poll to consume. Plaintext is KEK-wrapped at rest;
     * if no KEK is configured the plaintext lives briefly in cleartext
     * (logged as a warning at construction time on a real deployment).
     *
     * Returns true on success; false if the row is missing, expired,
     * or already non-pending (caller should re-lookup and surface a
     * clearer error).
     */
    approve(input: ApproveInput): boolean;
    /**
     * Mark a pending row rejected. Returns true on success; same
     * semantics as `approve` for race-with-other-mutator outcomes.
     */
    reject(input: RejectInput): boolean;
    /**
     * Single-call atomic poll resolver: looks up the row by device-
     * code hash, applies the four RFC 8628 outcomes, and consumes
     * (deletes) the row on `approved` so a replay returns
     * `expired_token`.
     *
     * `slow_down` is signaled when two consecutive polls land within
     * `SLOW_DOWN_THRESHOLD_MS` while the row is still pending — the
     * CLI is supposed to back off by `interval += 5` seconds on this
     * response.
     */
    pollByDeviceCode(deviceCode: string): PollOutcome;
    /**
     * List all currently-pending (and not-yet-expired) enrollment
     * rows, oldest first. Used by the director's pending-approvals
     * panel.
     */
    listPending(): EnrollmentRow[];
    /**
     * Best-effort cleanup of expired rows. Called periodically by a
     * reaper running on the same loop as `sessions.purgeExpired()`.
     */
    purgeExpired(): number;
}

/**
 * Key-encryption-key (KEK) resolution + field-level AES-256-GCM
 * primitives for wrapping TOTP secrets and the VAPID private key
 * at rest.
 *
 * The team config file (`ac7.json`) stores bearer tokens as
 * SHA-256 hashes, but until this module landed it stored TOTP secrets
 * (base32) and the VAPID private key (PEM) as plaintext. That was the
 * second HIGH-severity finding in the 2026-04-16 audit: a read-only
 * exfiltration of `ac7.json` leaked every slot's web-login
 * credential permanently (rotating would have required re-enrolling
 * every slot from scratch).
 *
 * This module provides two building blocks:
 *
 *   1. `resolveKek()` — returns the 32-byte key to use for field
 *      encryption. Order of precedence:
 *        (a) `AC7_KEK` env var — base64 / base64url of exactly 32 bytes
 *        (b) a persisted key file at `<configDir>/ac7-kek.bin`
 *            (`0o600`). Auto-generated on first resolve if missing.
 *      Either source alone is enough; individual-contributors who want the KEK
 *      managed by their OS keychain / secret manager inject it via
 *      `AC7_KEK`. Zero-config self-host users get the file-based
 *      default.
 *
 *   2. `encryptField()` / `decryptField()` — wrap a plaintext value
 *      with AES-256-GCM (12-byte random IV, 16-byte auth tag) and
 *      emit / parse a single opaque string of shape
 *      `enc-v1:<base64url(iv)>:<base64url(tag)>:<base64url(ct)>`.
 *      The `enc-v1:` prefix makes it trivial to distinguish from
 *      the legacy plaintext formats (base32 TOTP secret, PEM VAPID
 *      key) when migrating.
 *
 * Design notes:
 *
 *   - AES-256-GCM is authenticated: a tampered ciphertext fails
 *     decrypt with a clear error, not a silent bit flip. This closes
 *     the "attacker also has write access to the config file" sub-
 *     case of the same threat model.
 *   - The KEK is never persisted alongside the config file — the
 *     default key location is `<configDir>/ac7-kek.bin`, and we
 *     recommend individual-contributors chmod the containing directory (`0o700`)
 *     rather than relying on the individual file's `0o600`.
 *   - Single format version (`enc-v1`). A future format will bump
 *     the prefix (`enc-v2:`...) and both loaders will coexist for
 *     one release window.
 *   - `resolveKek` is synchronous — called once at server boot.
 *     Callers that run without a server (e.g. the CLI `rotate`
 *     subcommand) don't need it.
 */
declare const ENCRYPTED_FIELD_PREFIX = "enc-v1:";
declare class KekResolutionError extends Error {
    constructor(message: string);
}
declare class EncryptedFieldError extends Error {
    constructor(message: string);
}
/**
 * Resolve the KEK to use for field-level encryption.
 *
 * @param configPath Absolute path of the team config file — used
 *   only to derive the default key-file location (alongside the
 *   config, in the same directory). Not read.
 * @param env Process env map; defaults to `process.env`. Allows tests
 *   to pin a specific KEK via `AC7_KEK`.
 */
declare function resolveKek(configPath: string, env?: NodeJS.ProcessEnv): Buffer;

/**
 * Parse a human-readable duration string into milliseconds.
 *
 * Accepted shapes: `<number><unit>` with units `ms`, `s`, `m`, `h`, `d`.
 * Whitespace between the number and the unit is allowed. Case-
 * insensitive.
 *
 * Returns null for anything that doesn't match — callers should
 * surface a usage error with a suggested shape rather than silently
 * defaulting.
 *
 * Lives in its own file (rather than alongside the SQLite activity
 * store) so tests can exercise it under any Node version — the rest
 * of the server module graph transitively depends on `node:sqlite`,
 * which requires Node ≥22.
 */
declare function parseDurationMs(input: string): number | null;

/**
 * Member activity stream store.
 *
 * Append-only timeline per member, capturing everything the member's
 * runner observed: LLM exchanges through the MITM proxy, opaque HTTP
 * calls to non-Anthropic endpoints, and objective lifecycle markers
 * (`objective_open` / `objective_close`). Objective traces are a
 * view over this stream — you query by time range bounded by the
 * markers for a given objectiveId.
 *
 * The store is a thin wrapper around SQLite plus an in-process
 * `EventEmitter` that the SSE endpoint subscribes to for live tail.
 * Appends fire the emitter synchronously after the insert commits,
 * so a subscriber attached during an append never misses a row — and
 * a subscriber that attaches AFTER an append can pull the tail via
 * `list()` and merge with the live stream, if the client cares about
 * zero gaps.
 *
 * Payloads are stored as JSON blobs (`event_json`). The server
 * doesn't introspect them beyond validating the discriminator at the
 * app layer; everything else is the SDK's responsibility.
 */

type ListActivityFilter = ListActivityFilter$1;
type ActivityStore = ActivityStore$1;
declare class SqliteActivityStore implements ActivityStore$1 {
    private readonly db;
    private readonly insertStmt;
    private readonly emitter;
    constructor(db: DatabaseSyncInstance);
    append(memberName: string, events: readonly ActivityEvent[]): ActivityRow[];
    list(filter: ListActivityFilter): ActivityRow[];
    subscribe(memberName: string, listener: ActivityListener): () => void;
    /**
     * Delete every activity row older than `cutoffTs` (by `event.ts`).
     * Returns the number of rows deleted. Not part of the core
     * `ActivityStore` interface — a non-persistent backend has nothing
     * to prune — but surfaced on the SQLite impl for the
     * `ac7 prune-traces` CLI and any future background-sweep timer.
     */
    prune(cutoffTs: number): number;
}
type SqliteActivityStoreHandle = SqliteActivityStore;
declare function createSqliteActivityStore(db: DatabaseSyncInstance): SqliteActivityStoreHandle;
/**
 * Stand-alone helper for `ac7 prune-traces` that opens the activity
 * DB, prunes, and closes — without spinning up a full `runServer`.
 */
declare function pruneActivityDb(db: DatabaseSyncInstance, cutoffTs: number): number;

/**
 * Objectives store — SQLite-backed CRUD + state machine for the v1
 * objectives primitive. Lives alongside the event log, session store,
 * and push-subscription store, sharing the same `DatabaseSync` handle.
 *
 * Design notes:
 *
 * - **Push-assigned, single-assignee.** v1 objectives are created by an
 *   admin, operator, or lead-agent and immediately bound to one
 *   assignee. No unclaimed queue, no claim verb. Reassignment is an
 *   admin-only action.
 *
 * - **Four-state lifecycle.** `active | blocked | done | cancelled`.
 *   `done` and `cancelled` are terminal; `active ↔ blocked` is the only
 *   back-and-forth. The store enforces every transition so callers
 *   can't sneak an illegal state through.
 *
 * - **Outcome is contractual.** Every objective has a non-empty
 *   `outcome` field at creation — the tangible definition of done. The
 *   briefing composer and tool-description builder both surface it so
 *   the agent sees its acceptance criteria on every turn.
 *
 * - **Audit log via `objective_events`.** Every mutating call appends
 *   an event row in the same transaction as the state change. The
 *   table is append-only — there is no delete or update path.
 *
 * - **Discussion piggybacks on threads.** The store itself doesn't
 *   manage the auto-thread-per-objective; it just emits events that
 *   the app layer fans out as channel pushes on thread `obj:<id>`.
 *   Separation of concerns keeps the store free of broker knowledge.
 */

/**
 * Thrown when the store rejects a state transition or a caller-supplied
 * value. The server layer maps these to 400/409 HTTP responses.
 */
declare class ObjectivesError extends Error {
    readonly code: 'not_found' | 'invalid_transition' | 'invalid_input' | 'terminal';
    constructor(code: ObjectivesError['code'], message: string);
}
/**
 * Result of a mutating store call. Every mutation returns the updated
 * objective plus the list of events it appended in this call — one or
 * more, depending on the operation. The app layer iterates over
 * `events` when publishing channel pushes so the outbound notification
 * per-event matches the audit log entry-per-event exactly.
 *
 * Most operations emit a single event; `update` can emit up to two
 * (a status transition plus a note). A no-op update (status equals
 * current status, no note, no block reason change) emits zero events
 * and returns `events: []` — callers should treat empty-events as
 * "nothing worth broadcasting."
 */
interface ObjectivesMutationResult {
    objective: Objective;
    events: ObjectiveEvent[];
}
interface ObjectivesStore {
    /** List objectives filtered by assignee + status. Newest first. */
    list(filter?: {
        assignee?: string;
        status?: ObjectiveStatus;
    }): Objective[];
    /** Fetch a single objective or null if unknown. */
    get(id: string): Objective | null;
    /** Fetch the full append-only event history for an objective. */
    events(id: string): ObjectiveEvent[];
    /**
     * Create and assign an objective. The originator is the creating
     * caller's name. Emits an `assigned` event.
     */
    create(input: CreateObjectiveRequest, originator: string, now?: number): ObjectivesMutationResult;
    /**
     * Update status / note on an active or blocked objective. Never
     * transitions to `done` — use `complete` for that. Emits 0-2 events
     * depending on what actually changed.
     */
    update(id: string, input: UpdateObjectiveRequest, actor: string, now?: number): ObjectivesMutationResult;
    /** Mark done with a required result. Assignee-only (enforced upstream). */
    complete(id: string, input: CompleteObjectiveRequest, actor: string, now?: number): ObjectivesMutationResult;
    /** Terminally cancel. */
    cancel(id: string, input: CancelObjectiveRequest, actor: string, now?: number): ObjectivesMutationResult;
    /** Reassign to a different slot. */
    reassign(id: string, input: ReassignObjectiveRequest, actor: string, now?: number): ObjectivesMutationResult;
    /**
     * Add or remove watchers on an existing objective. Appends one
     * `watcher_added` event per new name and one `watcher_removed`
     * per removed name. No-op additions/removals are silently
     * dropped (deduped against the current list). Returns `events: []`
     * if the net change is empty.
     */
    updateWatchers(id: string, input: UpdateWatchersRequest, actor: string, now?: number): ObjectivesMutationResult;
    /**
     * Replace the `attachments` JSON column without producing an audit
     * event. Used by the server route after `create` to swap the
     * originally-claimed attachment paths (in member homes) for their
     * mirrored copies in the `/objectives/<id>/...` namespace. Returns
     * the updated objective.
     */
    setAttachments(id: string, attachments: Attachment[], now?: number): Objective;
}
declare function createSqliteObjectivesStore(db: DatabaseSyncInstance): ObjectivesStore;

/**
 * Boot-time server config — the slim file at `ac7.json`.
 *
 * This file holds ONLY infrastructure knobs that need to exist before
 * SQLite can open. Team identity, members, and permission presets
 * live in the database (see team-store.ts) and are mutated through
 * the API/CLI/MCP surface, not by hand-editing JSON.
 *
 * What's here:
 *   - dbPath, activityDbPath, filesRoot — storage paths
 *   - https                              — TLS mode + cert paths
 *   - webPush                            — VAPID keypair (encrypted)
 *   - jwt                                — federated JWT issuer config
 *   - files                              — upload caps + blob root
 *
 * What's NOT here:
 *   - team.name / directive / context, permission presets   → DB
 *   - members[]                                            → DB
 *   - tokenHash, totpSecret                                → DB
 *
 * Hand-editing this file is supported as the "rescue path" for
 * deployment plumbing only. Identity changes go through the API.
 */

declare const ServerConfigSchema: z.ZodObject<{
    _comment: z.ZodOptional<z.ZodUnknown>;
    dbPath: z.ZodOptional<z.ZodString>;
    activityDbPath: z.ZodOptional<z.ZodString>;
    filesRoot: z.ZodOptional<z.ZodString>;
    https: z.ZodOptional<z.ZodObject<{
        mode: z.ZodDefault<z.ZodEnum<{
            off: "off";
            "self-signed": "self-signed";
            custom: "custom";
        }>>;
        bindHttp: z.ZodDefault<z.ZodNumber>;
        bindHttps: z.ZodDefault<z.ZodNumber>;
        redirectHttpToHttps: z.ZodDefault<z.ZodBoolean>;
        hsts: z.ZodDefault<z.ZodEnum<{
            off: "off";
            auto: "auto";
            on: "on";
        }>>;
        selfSigned: z.ZodDefault<z.ZodObject<{
            lanIp: z.ZodDefault<z.ZodNullable<z.ZodString>>;
            validityDays: z.ZodDefault<z.ZodNumber>;
            regenerateIfExpiringWithin: z.ZodDefault<z.ZodNumber>;
        }, z.core.$strip>>;
        custom: z.ZodDefault<z.ZodObject<{
            certPath: z.ZodDefault<z.ZodNullable<z.ZodString>>;
            keyPath: z.ZodDefault<z.ZodNullable<z.ZodString>>;
        }, z.core.$strip>>;
    }, z.core.$strip>>;
    webPush: z.ZodOptional<z.ZodObject<{
        vapidPublicKey: z.ZodString;
        vapidPrivateKey: z.ZodString;
        vapidSubject: z.ZodDefault<z.ZodString>;
    }, z.core.$strip>>;
    jwt: z.ZodOptional<z.ZodObject<{
        issuer: z.ZodString;
        jwksUrl: z.ZodString;
        audience: z.ZodString;
    }, z.core.$strip>>;
    files: z.ZodOptional<z.ZodObject<{
        root: z.ZodOptional<z.ZodString>;
        maxFileSize: z.ZodOptional<z.ZodNumber>;
    }, z.core.$strip>>;
}, z.core.$strip>;
interface ServerConfig {
    dbPath: string | null;
    activityDbPath: string | null;
    filesRoot: string | null;
    https: HttpsConfig | null;
    webPush: WebPushConfig | null;
    jwt: JwtConfig | null;
    files: FilesConfig | null;
}
/**
 * Read and validate the server config file. Throws
 * `ConfigNotFoundError` on ENOENT and `MemberLoadError` for any
 * other failure (invalid JSON, schema violation).
 */
declare function loadServerConfigFromFile(path: string): ServerConfig;
/**
 * Persist the server config atomically with `chmod 0o600`. The webPush
 * private key is expected to already be encrypted (`enc-v1:...`) by
 * the caller — we don't crypt at this layer.
 */
declare function writeServerConfigFile(path: string, config: ServerConfig): void;
/**
 * Patch the on-disk server config in place. Reads the existing file,
 * applies a partial update, and rewrites atomically. Used by the
 * VAPID auto-generation path on first boot.
 */
declare function updateServerConfigFile(path: string, patch: Partial<ServerConfig>): ServerConfig;
/**
 * Resolve a path field from a loaded ServerConfig against the config
 * file's own directory. Relative paths in the config refer to
 * locations adjacent to the config file — *not* the cwd of whoever is
 * reading it. This matters because the config file is often loaded
 * from one cwd (the wizard, run from the repo root) and consumed from
 * another (the broker, spawned from `apps/server/`); without anchoring,
 * `./ac7.db` resolves to two different files.
 *
 * Behavior:
 *   - `null` → `null` (no value to resolve)
 *   - absolute → returned verbatim
 *   - relative → joined onto `dirname(configPath)`
 */
declare function resolveConfigPath(configPath: string, value: string | null): string | null;

/**
 * SQLite-backed session store for human web-UI auth.
 *
 * A session is a server-issued capability: after a human completes
 * TOTP verification, we mint a random `id`, store a row binding that
 * id to a user name, and return it as an HttpOnly cookie. Every
 * subsequent request presenting the cookie resolves back to the user
 * via the dual-auth middleware, same as a bearer-token request.
 *
 * Lifetime: sliding 7-day TTL. Every `touch()` bumps `last_seen` and
 * extends `expires_at`. Sessions older than their `expires_at` are
 * treated as nonexistent on read and purged by `purgeExpired()`.
 *
 * The store does NOT own the DB handle — `runServer` opens one shared
 * `DatabaseSync` via `openDatabase()` and passes it here alongside the
 * event log.
 */

declare const SESSION_COOKIE_NAME = "ac7_session";
/** 7 days. Sliding — every API call resets the window. */
declare const SESSION_TTL_MS: number;
interface SessionRow {
    id: string;
    memberName: string;
    createdAt: number;
    expiresAt: number;
    lastSeen: number;
    userAgent: string | null;
}
declare class SessionStore {
    private readonly db;
    private readonly insertStmt;
    private readonly selectStmt;
    private readonly touchStmt;
    private readonly deleteStmt;
    private readonly purgeStmt;
    private readonly now;
    constructor(db: DatabaseSyncInstance, options?: {
        now?: () => number;
    });
    /**
     * Mint a fresh session for `memberName`. Returns the row so the
     * caller can put the `id` in a Set-Cookie header and return the
     * `expiresAt` to the SPA.
     */
    create(memberName: string, userAgent: string | null): SessionRow;
    /**
     * Look up a session by id. Returns null if the row doesn't exist or
     * is expired. Expired rows are not eagerly deleted here; that's the
     * periodic `purgeExpired()` job's responsibility.
     */
    get(id: string): SessionRow | null;
    /**
     * Bump `last_seen` and extend `expires_at` for an existing session.
     * Called on every authenticated request the session carries, so the
     * TTL slides as long as the user stays active.
     */
    touch(id: string): void;
    /** Delete a specific session (logout). No-op if the id doesn't exist. */
    delete(id: string): void;
    /**
     * Best-effort cleanup of expired rows. Safe to call periodically and
     * on shutdown. Not called on every request — we don't want auth
     * latency to include a DELETE.
     */
    purgeExpired(): number;
}

/**
 * SQLite-backed team + members + permission-preset store.
 *
 * Replaces the legacy ac7.json team/member surface. Three tables live
 * here:
 *
 *   - `team`               singleton row (id=1) carrying name, directive, context
 *   - `permission_presets` named bundles of leaf permissions
 *   - `members`            roster: name, role, instructions, raw_permissions,
 *                          totp enrollment, insertion order
 *
 * Auth tokens still live in the `tokens` table (see tokens.ts). Member
 * creation here does NOT issue a token — callers compose the two
 * stores: insert a member row, then issue a `tokens` row whose
 * `member_name` references it. Member deletion removes the row here
 * and asks the token store to revoke every token for the name.
 *
 * `raw_permissions` is stored verbatim (preset names or leaf strings);
 * the resolved leaf array is computed on every read against the
 * current presets. That means mutating a preset takes effect for every
 * member that references it on the next `findByName` / `members()`
 * call — no eager re-resolve, no stale cache.
 *
 * TOTP secrets are encrypted at rest when a process-wide KEK is set
 * (see kek.ts). Reads transparently decrypt; writes transparently
 * encrypt. The on-disk shape is `enc-v1:...` when wrapped, plaintext
 * base32 otherwise.
 */

/**
 * Read-side projection for the team config.
 *
 * Both the `Team` (wire) and a fresh `permissionPresets` snapshot are
 * baked in. Callers that mutate presets are expected to invalidate any
 * cached projection they hold; the store always returns a fresh copy.
 */
declare class TeamStore {
    private readonly db;
    private readonly getTeamStmt;
    private readonly upsertTeamStmt;
    private readonly listPresetsStmt;
    private readonly upsertPresetStmt;
    private readonly deletePresetStmt;
    private readonly now;
    constructor(db: DatabaseSyncInstance, options?: {
        now?: () => number;
    });
    /**
     * Materialize the current team config. Throws if the singleton row
     * has never been seeded — callers must run the wizard or
     * `seedTeam` first.
     */
    getTeam(): Team;
    /** True iff the team singleton row exists. Fast existence check. */
    hasTeam(): boolean;
    getPresets(): PermissionPresets;
    /**
     * Create or replace the team singleton. Validates name/directive/context
     * lengths via the shared zod-derived helpers.
     */
    setTeam(input: {
        name: string;
        directive: string;
        context: string;
    }, by?: string | null): Team;
    /** Patch the team singleton; only the supplied fields change. */
    updateTeam(patch: {
        name?: string;
        directive?: string;
        context?: string;
    }, by?: string | null): Team;
    /**
     * Insert or replace a permission preset. The `permissions` array is
     * validated against the canonical leaf set; unknown leaves throw
     * `MemberLoadError`. The preset NAME is also validated against the
     * preset-key regex.
     */
    setPreset(name: string, permissions: Permission[], by?: string | null): void;
    /**
     * Delete a permission preset. Returns true if a row was removed.
     * Note: members may still reference this preset in `raw_permissions`;
     * after deletion their resolved permissions exclude these leaves
     * silently. The caller is expected to gate destructive removal on
     * an admin permission and surface the dependency to the operator.
     */
    deletePreset(name: string): boolean;
    /**
     * List members that reference a preset by name in their
     * `raw_permissions`. Cheap scan — the roster is small (tens, not
     * thousands). Used by the destructive-delete confirmation path.
     */
    membersReferencingPreset(presetName: string, members: MemberStore): string[];
}
/**
 * Open both stores on a shared database handle. Boot-time helper.
 */
declare function openTeamAndMembers(db: DatabaseSyncInstance, options?: {
    now?: () => number;
}): {
    team: TeamStore;
    members: MemberStore;
};
/**
 * Test/wizard helper: build a SqliteMemberStore directly. Production
 * code paths should use `openTeamAndMembers` so the team store is
 * shared.
 */
declare function createSqliteMemberStore(db: DatabaseSyncInstance, teamStore: TeamStore, options?: {
    now?: () => number;
}): MemberStore;

declare const TOKEN_HASH_PREFIX = "sha256:";
/**
 * Hash a raw bearer token into the on-disk representation. Same
 * algorithm as the legacy `members.hashToken` so values round-trip
 * cleanly across the migration.
 */
declare function hashRawToken(rawToken: string): string;
/**
 * Generate a fresh cryptorandom bearer token in the standard
 * `ac7_<base64url>` format. 32 raw bytes → 43-char payload (~256 bits).
 *
 * Identical to `members.generateMemberToken` — re-exported here so
 * the new auth surface doesn't have to import from `members.ts`
 * (avoids a soft circular dep when `members.ts` later wants to call
 * back into the token store for boot-time seeding).
 */
declare function generateBearerToken(): string;
/**
 * `TokenInfo` plus the on-disk `hash` — internal-only. The wire shape
 * never carries the hash (it's a credential surface), but the auth
 * resolver needs it to confirm the row matches the presented token.
 */
interface InternalTokenRow extends TokenInfo {
    hash: string;
}
interface InsertTokenInput {
    memberName: string;
    rawToken: string;
    label?: string;
    origin?: TokenOrigin;
    /** Epoch ms; null/undefined = no expiry. */
    expiresAt?: number | null;
    /** Member name that issued this token, or null for bootstrap. */
    createdBy?: string | null;
}
/**
 * Migration-only insertion path. Used by `runServer` at boot to copy
 * a member's `tokenHash` from `ac7.json` into the token store with
 * `origin = 'bootstrap'`. The plaintext is unknown (we only have the
 * hash on disk), so the regular `insert` path is unusable here.
 *
 * Idempotent: if the hash is already present (re-boot, re-migration)
 * the insertion is skipped and the existing row is returned.
 */
interface InsertHashedTokenInput {
    memberName: string;
    /** Pre-computed `sha256:<hex>` hash. */
    hash: string;
    label?: string;
    origin?: TokenOrigin;
    expiresAt?: number | null;
    createdBy?: string | null;
}
declare class TokenStore {
    private readonly db;
    private readonly insertStmt;
    private readonly findByHashStmt;
    private readonly findByIdStmt;
    private readonly listForMemberStmt;
    private readonly touchStmt;
    private readonly deleteStmt;
    private readonly deleteForMemberStmt;
    private readonly purgeStmt;
    private readonly now;
    /**
     * Most-recent `last_used_at` write per token id. Lets us skip a DB
     * round-trip when the previous touch was within the debounce
     * window. Bounded by the live token count, which is tiny (one row
     * per active credential).
     */
    private readonly lastTouched;
    constructor(db: DatabaseSyncInstance, options?: {
        now?: () => number;
    });
    /**
     * Insert a fresh token row. Returns the public projection plus the
     * on-disk hash so the caller can pair it with the plaintext for
     * one-time display. Throws on UNIQUE-violation if an identical hash
     * already exists (~ infinitesimal probability — surface as a 500-
     * level retryable error at the caller).
     */
    insert(input: InsertTokenInput): InternalTokenRow;
    /**
     * Migration helper: insert a row from a pre-computed hash. Used by
     * `runServer` when seeding the store from `ac7.json`'s legacy
     * `tokenHash` field — the plaintext was lost when the JSON was
     * first written. Idempotent: if a row with this hash already
     * exists the existing row is returned (which makes re-boots and
     * concurrent migrations safe).
     */
    insertHashed(input: InsertHashedTokenInput): InternalTokenRow;
    /**
     * Resolve a presented bearer plaintext to an active token row.
     * Returns null when:
     *   - no row matches the hash
     *   - the row matches but has an `expires_at` in the past
     *
     * Constant-time-ish: the hash lookup is a primary-key SQL fetch so
     * the wall-clock cost is bounded by SQLite's index lookup, not by
     * a Map.get() chain timing variance.
     */
    resolve(rawToken: string): InternalTokenRow | null;
    /**
     * Find a token row by id (for revoke). Does not check expiry — an
     * admin may want to revoke an already-expired row to clean it up
     * before the periodic purge runs.
     */
    findById(id: string): InternalTokenRow | null;
    /**
     * List every active (non-expired) token for `memberName`, oldest
     * first. Used by the admin/self listing endpoint.
     */
    listForMember(memberName: string): TokenInfo[];
    /**
     * Bump `last_used_at` for `id`. Coalesces to one DB write per
     * `LAST_USED_DEBOUNCE_MS` window; further calls within the window
     * are no-ops. Intended to be called after every successful auth.
     */
    touch(id: string): void;
    /** Force a `last_used_at` write — bypasses the debounce. Used by tests. */
    touchNow(id: string): void;
    /**
     * Delete a specific token row. Returns true if a row was deleted.
     * Caller is expected to wrap this in its own permission check
     * (members.manage or self).
     */
    revoke(id: string): boolean;
    /**
     * Delete every token belonging to a member. Used during member
     * deletion so an in-flight token can't outlive its identity.
     * Returns the number of rows removed.
     */
    revokeAllForMember(memberName: string): number;
    /**
     * Best-effort cleanup of expired rows. Safe to call periodically
     * and on shutdown.
     */
    purgeExpired(): number;
}

/**
 * Generate a fresh base32 secret suitable for pasting into an
 * authenticator app or encoding into an `otpauth://` URI.
 */
declare function generateSecret(): string;
/**
 * Build the `otpauth://totp/...` URI that Google Authenticator, Authy,
 * 1Password, etc. scan from a QR code. The `issuer` shows up as the
 * account group ("ac7"); `label` is the account name inside that
 * group (typically the name).
 */
declare function otpauthUri(opts: {
    secret: string;
    issuer: string;
    label: string;
}): string;
/**
 * Compute the TOTP code for `secret` at `timestamp`. Only used by
 * tests and by the enrollment flow that lets individual-contributors verify their
 * phone is set up before persisting the secret.
 */
declare function currentCode(secret: string, timestamp?: number): string;
type VerifyResult = {
    ok: true;
    counter: number;
} | {
    ok: false;
    reason: 'malformed' | 'invalid' | 'replay';
};
/**
 * Verify `code` against `secret`, rejecting reuse. `lastCounter` is
 * the counter of the last code this slot accepted (0 for never). On
 * success the returned `counter` should be persisted as the new
 * `lastCounter` before the session is created.
 *
 * Returns:
 *   - `{ ok: true, counter }`                   — caller persists counter, creates session
 *   - `{ ok: false, reason: 'malformed' }`      — not 6 digits, or secret isn't valid base32
 *   - `{ ok: false, reason: 'invalid' }`        — code doesn't match for any period in window
 *   - `{ ok: false, reason: 'replay' }`         — code valid, but already accepted (or older)
 */
declare function verifyCode(secret: string, code: string, lastCounter: number, now?: number): VerifyResult;

/**
 * First-run interactive wizard for the ac7 broker.
 *
 * Triggered when the server boots without a config file at the
 * expected path AND stdin is a TTY. Walks the operator through
 * creating a team (name, directive, context) and the first admin
 * member, generates a random bearer token, and auto-enrolls the
 * admin in TOTP (admins need web UI login by default).
 *
 * The wizard is I/O only: it returns the captured data and lets the
 * caller decide where to persist it. Today that caller is the boot
 * entry (`index.ts`), which inserts the team + admin into SQLite via
 * the team/member/token stores and writes a slim infra-only
 * `ac7.json` to disk.
 *
 * Subsequent members are added by the admin through the web UI
 * (Members admin page) or the CLI (`ac7 member create`).
 */

interface WizardIO {
    prompt(question: string): Promise<string>;
    println(line: string): void;
    /** Best-effort wipe of the last N lines, for TOTP secret redaction. */
    redactLines?(count: number): void;
    isInteractive: boolean;
}
interface RunWizardOptions {
    configPath: string;
    io: WizardIO;
    tokenFactory?: () => string;
    totpSecretFactory?: () => string;
    now?: () => number;
    qrRenderer?: (uri: string) => string;
}
/**
 * Captured wizard data, ready to seed the DB-backed stores. The
 * caller is responsible for inserting the team + presets + admin into
 * SQLite (via `TeamStore` / `MemberStore` / `TokenStore`) and writing
 * the slim infra-only config file.
 */
interface WizardResult {
    team: Team;
    admin: {
        name: string;
        role: Role;
        instructions: string;
        rawPermissions: string[];
        permissions: Permission[];
        /** Plaintext bearer token — show once, then hash and discard. */
        token: string;
        /** Plaintext base32 TOTP secret — caller encrypts before persisting. */
        totpSecret: string;
    };
}
/**
 * Drive the wizard to completion. Throws `MemberLoadError` if IO is
 * not interactive — the CLI catches that and prints a non-interactive
 * hint instead.
 */
declare function runFirstRunWizard(options: RunWizardOptions): Promise<WizardResult>;
declare function createTtyWizardIO(stdin?: NodeJS.ReadStream, stdout?: NodeJS.WriteStream): {
    io: WizardIO;
    close: () => void;
};

/**
 * `@agentc7/server` library entry.
 *
 * Exposes `runServer()` so the CLI can start the broker in-process
 * without spawning a subprocess. Keeps side effects minimal; the
 * caller owns signal handling and process.exit semantics.
 *
 * HTTPS modes:
 *   - off          → plain HTTP on `bindHttp` (default; localhost)
 *   - self-signed  → HTTP/2+TLS on `bindHttps` with a cert stored
 *                    under `<configDir>/certs/`. Optional HTTP→HTTPS
 *                    308 redirect listener on `bindHttp`.
 *   - custom       → HTTP/2+TLS with user-supplied cert/key paths
 *
 * HTTP/2 is the default transport because browsers cap HTTP/1.1 at
 * 6 concurrent connections per origin — users with many tabs open
 * would starve the 7th. HTTP/2 multiplexes requests over one
 * connection. WebSocket upgrades currently fall back to HTTP/1.1
 * via ALPN; `allowHTTP1: true` keeps that path working.
 */

interface RunServerOptions {
    /**
     * Team and members are sourced from the database (see team-store.ts).
     * `runServer` opens its own DB handle on `dbPath` and refuses to
     * boot if the team singleton row is missing — the caller (CLI entry
     * or wizard flow) is responsible for seeding the DB beforehand.
     */
    /**
     * HTTPS configuration. Omit or pass a mode:'off' config to run
     * plain HTTP. For self-signed mode the caller must also pass
     * `configDir` so we know where to persist the cert.
     */
    https?: HttpsConfig;
    /**
     * Existing VAPID credentials from the team config file. When
     * `null` or omitted AND `configPath` is set, runServer() will
     * auto-generate a fresh keypair and persist it back to the
     * config file. Set explicitly to skip Web Push entirely.
     */
    webPush?: WebPushConfig | null;
    /**
     * Federated-JWT configuration. When non-null, the auth middleware
     * verifies bearer tokens with JWT structure against the configured
     * issuer's JWKS. Null (or omitted) → the JWT path is dormant and
     * only opaque bearer tokens + session cookies authenticate. The
     * CLI entry point passes `TeamConfig.jwt` straight through; library
     * consumers can construct this programmatically.
     */
    jwt?: JwtConfig$1 | null;
    /**
     * Path to the team config file — required only when auto-generating
     * VAPID keys on first boot, since we need to know where to write
     * the new `webPush` block. Loaders (`loadTeamConfigFromFile` +
     * the CLI entry) already know this path; lib consumers that
     * construct RunServerOptions by hand can pass it explicitly.
     */
    configPath?: string;
    /**
     * Directory to store cert files in when `https.mode === 'self-signed'`.
     * Required for self-signed mode, ignored otherwise. Typically
     * `dirname(configPath)` so certs sit next to the team config.
     */
    configDir?: string;
    /**
     * Absolute path to the built `@agentc7/web` bundle to serve as
     * the SPA. Defaults to `<dist>/../public` — i.e., `apps/server/public`
     * relative to the built `run.js`. Pass `null` to disable SPA
     * serving entirely (useful for tests and machine-only deployments).
     */
    publicRoot?: string | null;
    /**
     * Convenience override for the HTTP listener port. When provided
     * and `https.mode === 'off'`, this wins over `https.bindHttp`.
     * Ignored when HTTPS is active (configure ports via `https.bindHttp`
     * and `https.bindHttps` directly in that case). Primarily for
     * tests and the existing CLI env-var path.
     */
    port?: number;
    host?: string;
    dbPath?: string;
    /**
     * Pre-opened main DB. Used by test fixtures that seed team + members
     * before boot — `:memory:` SQLite handles aren't shared across opens,
     * so passing the seeded handle here is the only way to keep that
     * setup in scope. Production callers (CLI entry, library consumers)
     * pass `dbPath` and let runServer open its own.
     */
    db?: DatabaseSyncInstance;
    /**
     * Root directory for the content-addressed blob store backing
     * filesystem attachments. Defaults to `./data/files`. The path is
     * created if missing; blobs land under `<root>/<hash-prefix>/...`
     * with an atomic temp-and-rename upload flow.
     */
    filesRoot?: string;
    /**
     * Per-file upload cap in bytes. Defaults to 25 MB; the broker
     * additionally caps at 1 GB regardless of caller value.
     */
    maxFileSize?: number;
    /**
     * Dedicated SQLite file for the agent-activity (trace) store.
     *
     * Activity rows are the heaviest-write path in the broker — every
     * captured LLM exchange and every opaque HTTP chunk lands here,
     * batched at up to 50 events / 64 KB / 500 ms per active agent.
     * Running it on its own `DatabaseSyncInstance` keeps trace writes
     * from contending with the main broker DB's write lock (events,
     * objectives, sessions, push-subs). Dan's 2026-04-16 audit Part 5
     * flagged single-connection contention as the first real ceiling
     * we'd hit under load.
     *
     * Semantics:
     *   - Omitted + `dbPath === ':memory:'`   → `:memory:` (separate handle, fully isolated).
     *   - Omitted + `dbPath` is a file path  → `<dbPath>-activity.db` alongside the main DB.
     *   - Explicit `:memory:`                → separate in-memory DB.
     *   - Explicit file path                  → opened as-is.
     */
    activityDbPath?: string;
    logger?: Logger;
    /**
     * Optional callback once all listeners are bound. Fires once per
     * run with info about the primary (HTTPS if enabled, else HTTP)
     * listener.
     */
    onListen?: (info: ListenInfo) => void;
}
interface ListenInfo {
    address: string;
    port: number;
    /** `http` or `https` — drives how the banner formats the URL. */
    protocol: 'http' | 'https';
    /** Populated for https modes. */
    cert?: LoadedCert;
    /** Port of the parallel HTTP→HTTPS redirect listener, if any. */
    redirectHttpPort?: number;
}
interface RunningServer {
    stop: () => Promise<void>;
    /** Primary listener port — HTTPS when enabled, else HTTP. */
    port: number;
    host: string;
    protocol: 'http' | 'https';
}
declare function runServer(options: RunServerOptions): Promise<RunningServer>;

export { type ActivityStore, type AddMemberInput, ConfigNotFoundError, DEFAULT_POLL_INTERVAL_S, type DatabaseSyncInstance, ENCRYPTED_FIELD_PREFIX, ENROLLMENT_TTL_MS, EncryptedFieldError, EnrollmentStore, type HttpsConfig, HttpsConfigError, type InsertTokenInput, type InternalTokenRow, type JwtConfig$1 as JwtConfig, type JwtVerifier, KekResolutionError, type ListenInfo, type LoadedCert, type LoadedMember, MemberLoadError, type MemberStore, ObjectivesError, type ObjectivesStore, type RunServerOptions, type RunWizardOptions, type RunningServer, SERVER_VERSION, SESSION_COOKIE_NAME, SESSION_TTL_MS, type ServerConfig, ServerConfigSchema, SessionStore, TOKEN_HASH_PREFIX, TeamStore, TokenStore, type UpdateMemberPatch, type VerifiedClaims, type WebPushConfig, type WizardIO, composeBriefing, createJwtVerifier, createSqliteActivityStore, createSqliteMemberStore, createSqliteObjectivesStore, createTtyWizardIO, currentCode as currentTotpCode, defaultConfigPath, defaultHttpsConfig, formatUserCode, generateBearerToken, generateMemberToken, generateSecret as generateTotpSecret, hashRawToken, hashToken, loadServerConfigFromFile, looksLikeJwt, normalizeUserCode, openDatabase, openTeamAndMembers, otpauthUri, parseDurationMs, pruneActivityDb, resolveConfigPath, resolveKek, resolvePermissions, runFirstRunWizard, runServer, setKek, teammatesFromMembers, updateServerConfigFile, verifyCode as verifyTotpCode, writeServerConfigFile };
