/** * platform-client.ts * * Typed HTTP client for the AI Guard Platform API (Phase 3/4 backend). * * Configuration (resolved from environment): * AI_GUARD_PLATFORM_URL — base URL of the platform API, e.g. https://api.example.com * AI_GUARD_API_KEY — bearer token for authentication * * Retry + back-off policy * ─────────────────────── * Transient errors (network failure, 429, 5xx) are retried up to MAX_ATTEMPTS * times with exponential back-off and ±20 % jitter. * * Attempt 1: immediate * Attempt 2: ~100 ms (base × 2^0) * Attempt 3: ~200 ms (base × 2^1) * * Offline (PLATFORM_UNAVAILABLE) behaviour * ───────────────────────────────────────── * When all retry attempts fail due to a network error (not an auth/plan * error) the client returns an OFFLINE result. Callers decide what to * do — the entitlements layer applies an "offline grace policy": * * - Free-tier actions (scan.run on local paths) → ALLOW with warning * - Paid-gated actions (github.scan, ci.delta) → DENY with clear message * * Error contract * ────────────── * Hard errors (4xx except 429) are NOT retried and surface as AppError with * stable reason codes that map directly to the platform API decision codes: * * SUBSCRIPTION_INACTIVE → exit 2 * QUOTA_EXCEEDED → exit 2, include upgrade hint * FEATURE_NOT_IN_PLAN → exit 2, include upgrade hint * ROLE_NOT_ALLOWED → exit 2 * PLATFORM_OFFLINE → caller-handled (see above) */ import type { FindingFeedbackVerdict, SignedRulePack } from "./sdk/types"; export declare function getPlatformUrl(): string | undefined; export declare function hasPlatformApi(): boolean; export type DecisionReasonCode = "ACTION_ALLOWED" | "PAYMENT_REQUIRED" | "SUBSCRIPTION_INACTIVE" | "ROLE_NOT_ALLOWED" | "FEATURE_NOT_IN_PLAN" | "SEAT_LIMIT_EXCEEDED" | "QUOTA_EXCEEDED"; export type DecisionResult = { allow: boolean; reasonCode: DecisionReasonCode; reasonMessage: string; /** true when the result comes from offline-grace (no network round-trip) */ offline?: boolean; }; export type EntitlementsSnapshot = { planCode: string; subscriptionStatus: string | null; features: Record; seatLimit: number | null; monthlyScanLimit: number | null; }; export type QuotaStatus = { metricKey: string; used: number; limit: number | null; percentUsed: number | null; warnPercent: number; softWarning: boolean; hardExceeded: boolean; }; export type RecordUsageResult = { accepted: boolean; queued: boolean; /** true when the recording was skipped (offline) */ offline?: boolean; }; export type CurrentUsageResult = { planCode: string; monthKey: string; quotas: QuotaStatus[]; }; export type FeedbackIngestResult = { accepted: boolean; feedbackId?: string; queued?: boolean; offline?: boolean; }; export type RuntimeFindingIngestInput = { idempotencyKey: string; findingId: string; spanId?: string; ruleId: string; severity: string; sourceType: string; sourceRef?: string; title?: string; summary?: string; status: "open" | "resolved" | "false_positive"; projectId: string; appId: string; agentId: string; environment: string; tenantId: string; route: string; sessionId: string; modelName: string; toolInvocationId: string; traceId: string; requestId?: string; occurredAt: string; metadata?: Record; }; export type RuntimeTraceSpanIngestInput = { idempotencyKey: string; traceId: string; spanId: string; parentSpanId?: string; spanName: string; spanKind: string; spanStatus: string; startTime: string; endTime?: string; durationMs?: number; requestId?: string; findingId?: string; projectId: string; appId: string; agentId: string; environment: string; tenantId: string; route: string; sessionId: string; modelName: string; toolInvocationId: string; attributes?: Record; }; export type RuntimeIngestResult = { accepted: boolean; idempotentReplay: boolean; offline?: boolean; }; export declare function getRuntimeIngestionSigningKey(): string | undefined; /** * Ask the platform whether a workspace is allowed to perform an action. * * Offline grace policy (when network is unavailable): * - "scan.run" → ALLOW with offline flag (local scans are never blocked offline) * - All paid actions → DENY with PLATFORM_OFFLINE code */ export declare function decide(opts: { baseUrl: string; apiKey: string | undefined; workspaceId: string; action: string; }): Promise; /** * Record a billable usage event. Fire-and-forget from CLI perspective — if * the platform is offline the event is silently dropped (the minute-bucket * event ID will cause deduplication when connectivity is restored via a * separate sync mechanism if needed). */ export declare function recordUsage(opts: { baseUrl: string; apiKey: string | undefined; workspaceId: string; eventId: string; action: string; quantity?: number; metadata?: Record; }): Promise; /** * Fetch current-month usage quota status. * Returns null when offline (callers should degrade gracefully). */ export declare function getCurrentUsage(opts: { baseUrl: string; apiKey: string | undefined; workspaceId: string; }): Promise; /** * Fetch current entitlements snapshot (plan, features, limits). * Returns null when offline. */ export declare function getCurrentEntitlements(opts: { baseUrl: string; apiKey: string | undefined; workspaceId: string; }): Promise; export declare function getLatestRulePack(opts: { baseUrl: string; apiKey: string | undefined; workspaceId: string; channel?: "stable" | "preview" | "emergency" | "fast"; canaryPopulationId?: string; }): Promise; export declare function submitFindingFeedback(opts: { baseUrl: string; apiKey: string | undefined; workspaceId: string; findingId?: string; ruleId?: string; verdict: FindingFeedbackVerdict; note?: string; expectedBehavior?: string; missedAttackSummary?: string; evidenceSnippet?: string; }): Promise; export declare function ingestRuntimeFinding(opts: { baseUrl: string; apiKey: string | undefined; workspaceId: string; signingKey: string; finding: RuntimeFindingIngestInput; }): Promise; export declare function ingestRuntimeTraceSpan(opts: { baseUrl: string; apiKey: string | undefined; workspaceId: string; signingKey: string; span: RuntimeTraceSpanIngestInput; }): Promise;