//#region src/integration-state.d.ts export declare const INTEGRATION_STEPS: readonly ["credential", "application", "schema", "mapping", "trigger", "sync", "widget"]; export type StepId = (typeof INTEGRATION_STEPS)[number]; /** `blocked` means "prerequisite unmet", never "broken" — see C-19. */ export type StepState = "done" | "active" | "blocked" | "failed" | "skipped"; export type IntegrationStatus = "not_configured" | "sdk_installed" | "awaiting_connection" | "connected" | "schema_discovered" | "mapping_required" | "trigger_required" | "ready_to_sync" | "syncing" | "healthy" | "warning" | "error" | /** * @deprecated No longer derived. Liveness is its own dimension — read * `ConnectionHealth` from `deriveLiveness`. Setup progress does not move * backwards just because an app stopped running. */ "disconnected"; export declare const INTEGRATION_STEP_LABELS: Record; /** Each step's prerequisites. Ordering is enforced by a test, not convention. */ export declare const STEP_PREREQUISITES: Record; export declare function stepIndex(id: StepId): number; export interface RunFact { at: number; message?: string; ok: boolean; /** * The HTTP status the host answered with, when there was one. * * Absent for a run that failed before any response existed, and for callers * that do not track it — so every read must treat `undefined` as "unknown", * never as "fine". */ status?: number; } /** * Where the reporting app is running, as the app itself understands it. * * `unknown` is a real value, not an absence: `detectHostEnvironment` refuses to * guess when nothing places the app, and a bare `node server.js` with no * NODE_ENV must still be able to connect and be shown as itself. Defaulting * such a host to `local_dev` would file a production box's faults under local * development, where nobody would look for them. */ export type HostEnvironment = "local_dev" | "preview" | "production" | "unknown"; export interface IntegrationFacts { discoveredTableCount: number; /** The environment the app reported on its last heartbeat. */ environment?: HostEnvironment | null; /** * Has this app ever checked in? Distinguishes "never installed" from "was * working, now silent" — the same absent heartbeat means different things, * and only one of them is diagnosed rather than set up. */ everConnected: boolean; hasCredential: boolean; /** * The cadence the app said it heartbeats at. Liveness windows are multiples * of this rather than a fixed constant, so an app that deliberately reports * slowly is not called dead for obeying its own configuration. */ heartbeatIntervalMs?: number | null; lastHeartbeatAt: number | null; lastRun: RunFact | null; /** * Whether any entity is mapped, expressed as a number for historical * reasons — every producer sets it to 0 or 1, never a real total. Read it as * a flag (`> 0`) and never render it as a quantity: doing so reported "1 * entity mapped" to a workspace with three. The true count lives on the * mappings list, which the What syncs tab reads directly. */ mappedEntityCount: number; now: number; /** * Deployment presence, when this deployment has one. Absent or `http` * transport keeps the cadence-derived behaviour every SDK in the field uses. */ presence?: DeploymentPresence | null; /** * The result of Clivly asking the app directly whether it is up. * * Every other signal here is *pushed* by the app. On a serverless host there * is no process between requests to push anything: the SDK's heartbeat timer * fires once at boot and is then frozen, so cadence alone reports a perfectly * healthy deployment as offline forever. A probe inverts that — Clivly asks, * and a host that answers is up by definition. * * Absent keeps the pure cadence behaviour, so nothing changes for apps that * do hold a long-lived process. */ reachabilityProbe?: ReachabilityProbe | null; /** * Faults the running app diagnosed about itself on its last heartbeat — an * unreadable database, a secret missing from the process environment. These * are what make a reachable app `live_with_issues`; unfinished setup is not. */ reportedIssues?: StepProblem[]; syncInFlight: boolean; /** A trigger URL and secret both exist, so Clivly can ask the app to sync. */ triggerConfigured: boolean; warnings: string[]; widgetInstalled: boolean; } export interface StepProblem { code: string; fix: string; message: string; } export interface IntegrationStep { blockedBy: StepId | null; evidence: string | null; id: StepId; label: string; problem: StepProblem | null; state: StepState; } export declare function formatAge(ms: number): string; /** The cadence `createClivlySDK` uses when the host app configures none. */ export declare const DEFAULT_HEARTBEAT_INTERVAL_MS = 60000; export type ConnectionHealth = "live" | "live_with_issues" | "stale" | "offline"; /** * Clivly's own answer to "is this app up", obtained by asking it rather than * waiting to be told. * * `reachable` is deliberately about the *connection*, not the response body: an * app that answers `401` has proved it is running just as conclusively as one * that answers `200`. Only silence — a timeout, a refused connection, DNS that * does not resolve — means offline. */ export interface ReachabilityProbe { /** When Clivly performed the check. Probe results are cached, so this ages. */ checkedAt: number; /** Did anything answer at the other end? */ reachable: boolean; } /** Which algorithm decides whether this deployment is up. */ export type PresenceTransport = "websocket" | "http"; export type PresenceConnectionState = "connected" | "reconnecting" | "disconnected"; /** * A deployment's connection state as its `PresenceRoom` last persisted it. * * This exists because a socket has an explicit state and a timestamp does not. * Deriving WebSocket liveness from heartbeat age would leave a disconnected app * shown as live for 150 seconds and offline only after ten minutes, which is * the whole reason this transport exists. */ export interface DeploymentPresence { /** Sockets currently accepted in the room. */ activeInstanceCount: number; connectionState: PresenceConnectionState; transport: PresenceTransport; } export interface ConnectionLiveness { /** `"8s ago"`, or `"No heartbeat yet"` when the app has never reported. */ ageLabel: string; ageMs: number | null; environment: HostEnvironment | null; health: ConnectionHealth; /** * Faults on a reachable app. Empty unless `health` is `live_with_issues`. */ issues: StepProblem[]; /** Present when a deployment-presence record supplied this liveness result. */ presence?: Pick; /** Is a heartbeat recent enough to speak for the app as it is right now? */ reachable: boolean; } /** * The two boundaries that separate live from stale from offline, in terms of * the app's own reported cadence. A cadence we cannot believe — absent, zero, * negative — falls back to the SDK default rather than producing a window of * zero that would call every app offline. */ export declare function livenessWindows(intervalMs: number | null | undefined): { liveMs: number; staleMs: number; }; export declare function deriveLiveness(facts: IntegrationFacts): ConnectionLiveness; /** * Walk the ladder once. A step is `blocked` when an earlier step it needs has * not been satisfied — a prerequisite statement, not a verdict about this step * — and only the first unsatisfied step is `active`, so exactly one root cause * ever surfaces. */ export declare function resolveSteps(facts: IntegrationFacts): IntegrationStep[]; /** * Collapse the ladder to the single status the surfaces switch on. This is the * setup dimension only: it answers how far configuration got, never whether the * app is up. Pair it with `deriveLiveness` for the second half of the picture. */ export declare function deriveStatus(steps: IntegrationStep[], facts: IntegrationFacts): IntegrationStatus; export interface NextAction { command: string | null; envFile: string | null; label: string; } export interface IntegrationState { /** * Is the app running right now. Deliberately alongside `status` rather than * folded into it: a surface must be able to say "setup reached mapping" and * "the app is offline" in the same breath without either overwriting the * other. */ liveness: ConnectionLiveness; next: NextAction | null; status: IntegrationStatus; steps: IntegrationStep[]; } /** * The command that installs Clivly into a host app. Exported because the connect * dialog prints it too, and it must be the same string as the one the ladder * recommends — that pairing is what C-05 broke. */ export declare const INSTALL_COMMAND = "npx clivly init"; /** * THE one producer of `next.command`. Six surfaces previously each invented * their own "run this next" string, and one of them named a read-only command * as the fix for a write problem (C-05). If you need a command for the * next-action slot, call this — do not write another one. */ export declare function nextAction(steps: IntegrationStep[], status: IntegrationStatus, liveness: ConnectionLiveness): NextAction | null; export declare function computeIntegrationState(facts: IntegrationFacts): IntegrationState; //#endregion