@omnicross/daemon 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { Logger, OutboundApiServerConfig, ProviderConfigSource, TransformerService, Transformer, ResolvedTransformerChain, ApiServerSettingsStore, PricingStore, AutomaticPricingSource, OutboundPermission, OutboundKeyDb, OutboundKeyDbRow, OutboundKeyPolicy, PricingEngine as PricingEngine$1, OpenAIOperationRegistry } from '@omnicross/core';
1
+ import { Logger, OutboundApiServerConfig, ProviderConfigSource, TransformerService, Transformer, ResolvedTransformerChain, ApiServerSettingsStore, PricingStore, AutomaticPricingSource, OutboundPermission, OutboundKeyDb, OutboundKeyDbRow, GatewayBindingTarget, OutboundKeyPolicy, PricingEngine as PricingEngine$1, OpenAIOperationRegistry } from '@omnicross/core';
2
2
  import { SearchRuntime, SearchFrontendModes } from '@omnicross/core/search';
3
3
  import { ApiKeyPoolService } from '@omnicross/core/completion/ApiKeyPoolService';
4
- import { AllowanceSchedulingConfig, AccountProbeConfig, ImageProviderId, OutboundKeyDb as OutboundKeyDb$1, VoucherDb, KeySpendReader, OutboundApiServer, ImagesServerConfig } from '@omnicross/core/outbound-api';
4
+ import { AllowanceSchedulingConfig, AccountProbeConfig, ImagesServerConfig, ImageProviderId, OutboundKeyDb as OutboundKeyDb$1, VoucherDb, KeySpendReader, OutboundApiServer } from '@omnicross/core/outbound-api';
5
5
  import { RouteLeaseManager, ProviderProxy } from '@omnicross/core/provider-proxy';
6
6
  import { UsageRecorder, PricingEngine } from '@omnicross/core/usage';
7
7
  import { SubscriptionCredentialStore, FetchLike, CodexImageCapabilityEvidenceSource, CodexImageCapabilityEvidenceRequest, CodexImageCapabilityEvidence, CodexImageCapabilityObservation, ImageExecutionScheduler, ImageExecutionAccountKey, ImageExecutionSchedulerRequest, ImageExecutionSchedulerGrant, SubscriptionAccountService, SubscriptionProviderRegistry } from '@omnicross/subscriptions';
@@ -21,8 +21,8 @@ import { AuditRecord, AuditStats, AuditBodyResult, AuditConfig } from '@omnicros
21
21
  import { BillingDeliveryStatus, BillingConfig, BillingEvent } from '@omnicross/contracts/billing-types';
22
22
  import http from 'node:http';
23
23
  import * as _omnicross_contracts_image_generation_types from '@omnicross/contracts/image-generation-types';
24
- import { ImageCapabilityUnavailableReason, ImageCapabilities, ImageGenerationErrorCode, ImageReferenceMetadata, ImageReferenceId } from '@omnicross/contracts/image-generation-types';
25
- import { ImageApiContributions, ResponsesImageGenerationContribution, ResponsesImageInspectionInput, ResponsesImageAdmission, ResponsesHostedToolSelection, ResponsesImageRequestScope, ImageTelemetrySink, ImageApiAuditRecord, ImageTelemetryRecord, ImageReferenceStore, ImageReferenceSaveInput, ImageReferenceResolution, ImageTemporaryResourceBudget, ImageTemporaryResourceBudgetLease, ImageApiLimits, ImageRequestResourceScope, ImageApiRuntimeResolver, RemoteImageAssetResolver, ImageProvider, ImageProviderRegistry, ImageOrchestrator } from '@omnicross/core/image-generation';
24
+ import { ImageReferenceMetadata, ImageReferenceId, ImageGenerationErrorCode, ImageCapabilityUnavailableReason, ImageCapabilities } from '@omnicross/contracts/image-generation-types';
25
+ import { ImageReferenceStore, ImageReferenceSaveInput, ImageReferenceResolution, ImageApiContributions, ResponsesImageGenerationContribution, ResponsesImageInspectionInput, ResponsesImageAdmission, ResponsesHostedToolSelection, ResponsesImageRequestScope, ImageTelemetrySink, ImageApiAuditRecord, ImageTelemetryRecord, ImageTemporaryResourceBudget, ImageTemporaryResourceBudgetLease, ImageApiLimits, ImageRequestResourceScope, ImageApiRuntimeResolver, RemoteImageAssetResolver, ImageProvider, ImageProviderRegistry, ImageOrchestrator } from '@omnicross/core/image-generation';
26
26
  import { LLMProvider, AgentDefaultModels, GlobalModelParameters } from '@omnicross/contracts/llm-config';
27
27
  import { PricingEntry, PricingEntryInput, PricingResolution, PricingSourceRefreshResult } from '@omnicross/contracts/pricing-types';
28
28
  import { ResponsesImageStateStore, ResponsesImageStateCommitInput, ResponsesImageCallBinding, ResponsesImageCallId, ResponsesImageCallResolution, ResponsesImageResponseResolution } from '@omnicross/core/image-generation/responses';
@@ -2263,6 +2263,119 @@ type AuditCompactor = () => {
2263
2263
  /** The read surface the AdminServer consumes (bootstrap binds it to the ledger dir). */
2264
2264
  type BillingStatusReader = () => BillingDeliveryStatus;
2265
2265
 
2266
+ /**
2267
+ * Codex session discovery and provider migration.
2268
+ *
2269
+ * Codex stores the human-readable rollout in JSONL files and keeps the index
2270
+ * used by `codex resume` in state_5.sqlite. These two stores must move
2271
+ * together: changing only one of them makes a session either appear under the
2272
+ * wrong provider or disappear from resume entirely.
2273
+ *
2274
+ * This module deliberately exposes metadata only. It never returns a JSONL
2275
+ * line, prompt, tool output, or response body to the admin API.
2276
+ */
2277
+ interface CodexSessionManagerOptions {
2278
+ /** Defaults to CODEX_HOME or the current user's `.codex` directory. */
2279
+ codexHome?: string;
2280
+ /** Defaults to `<codexHome>/state_5.sqlite`. */
2281
+ stateDatabasePath?: string;
2282
+ }
2283
+ interface CodexStateDatabaseStatus {
2284
+ path: string;
2285
+ available: boolean;
2286
+ reason?: string;
2287
+ }
2288
+ type CodexSessionStatus = 'ready' | 'missing_rollout' | 'unreadable_rollout';
2289
+ interface CodexSessionSummary {
2290
+ id: string;
2291
+ cwd: string;
2292
+ rolloutPath: string;
2293
+ provider: string | null;
2294
+ /** Provider from the JSONL session_meta record, when available. */
2295
+ jsonlProvider: string | null;
2296
+ model: string | null;
2297
+ createdAt: string | null;
2298
+ updatedAt: string | null;
2299
+ fileSize: number | null;
2300
+ fileModifiedAt: string | null;
2301
+ status: CodexSessionStatus;
2302
+ /** True when the session has a row in state_5.sqlite. */
2303
+ inStateDatabase: boolean;
2304
+ }
2305
+ interface CodexSessionListResult {
2306
+ projectPath: string;
2307
+ codexHome: string;
2308
+ stateDatabase: CodexStateDatabaseStatus;
2309
+ sessions: CodexSessionSummary[];
2310
+ warnings: string[];
2311
+ }
2312
+ interface CodexSessionProviderPlan {
2313
+ id: string;
2314
+ provider: string | null;
2315
+ model: string | null;
2316
+ rolloutPath: string;
2317
+ status: CodexSessionStatus | 'blocked';
2318
+ /** All provider values found in structured JSON properties. */
2319
+ providers: string[];
2320
+ /** Number of structured provider properties matching fromProvider. */
2321
+ matchingFields: number;
2322
+ /** Number of structured provider properties that would change. */
2323
+ changedFields: number;
2324
+ sqliteWillUpdate: boolean;
2325
+ action: 'update' | 'no_change' | 'blocked';
2326
+ reason?: string;
2327
+ }
2328
+ interface CodexSessionProviderPreview {
2329
+ projectPath: string;
2330
+ fromProvider: string | null;
2331
+ toProvider: string;
2332
+ stateDatabase: CodexStateDatabaseStatus;
2333
+ sessions: CodexSessionProviderPlan[];
2334
+ warnings: string[];
2335
+ }
2336
+ interface ApplyCodexSessionProviderInput {
2337
+ projectPath: string;
2338
+ sessionIds: string[];
2339
+ toProvider: string;
2340
+ fromProvider?: string;
2341
+ }
2342
+ interface CodexSessionProviderApplyResult {
2343
+ ok: true;
2344
+ projectPath: string;
2345
+ fromProvider: string | null;
2346
+ toProvider: string;
2347
+ updatedSessions: number;
2348
+ jsonlFiles: number;
2349
+ jsonlFields: number;
2350
+ sqliteRows: number;
2351
+ backups: string[];
2352
+ }
2353
+ declare class CodexSessionManagerError extends Error {
2354
+ constructor(message: string);
2355
+ }
2356
+ /**
2357
+ * The manager serializes mutations in one daemon process. This does not try
2358
+ * to lock Codex itself; the file snapshot check below still refuses to replace
2359
+ * a rollout that changed while it was being prepared.
2360
+ */
2361
+ declare class CodexSessionManager {
2362
+ readonly codexHome: string;
2363
+ readonly stateDatabasePath: string;
2364
+ private mutationTail;
2365
+ constructor(options?: CodexSessionManagerOptions);
2366
+ list(projectPath: string): Promise<CodexSessionListResult>;
2367
+ preview(input: Omit<ApplyCodexSessionProviderInput, 'toProvider'> & {
2368
+ toProvider: string;
2369
+ }): Promise<CodexSessionProviderPreview>;
2370
+ apply(input: ApplyCodexSessionProviderInput): Promise<CodexSessionProviderApplyResult>;
2371
+ private applyLocked;
2372
+ private withMutationLock;
2373
+ }
2374
+ declare function replaceStructuredProviderFields(value: unknown, fromProvider: string | undefined, toProvider: string, providers?: Set<string>): {
2375
+ matchingFields: number;
2376
+ changedFields: number;
2377
+ };
2378
+
2266
2379
  /**
2267
2380
  * ProviderKeyQuota — BYO provider-row key quota parsing (pure functions).
2268
2381
  *
@@ -2325,1551 +2438,1600 @@ interface ProviderKeyQuota {
2325
2438
  errorCode?: string;
2326
2439
  }
2327
2440
 
2328
- type PreparedImageRuntimeGeneration = {
2329
- readonly id: string;
2330
- readonly enabled: true;
2331
- readonly imageApi: ImageApiContributions;
2332
- readonly hosted: ResponsesImageGenerationContribution;
2333
- readonly hostedRuntime: HostedImageRuntimePolicy;
2334
- readonly inspectCapability?: (apiKeyId: string) => Promise<Omit<ImageRuntimeCapabilityInspection, 'generationId'>>;
2335
- readonly readRuntimeStatus?: () => ImageRuntimeResourceStatus;
2336
- dispose(): void | Promise<void>;
2337
- } | {
2338
- readonly id: string;
2339
- readonly enabled: false;
2340
- dispose(): void | Promise<void>;
2341
- };
2342
- interface HostedImageRuntimeGenerationLease {
2343
- readonly generationId: string;
2344
- /** Compatibility/debug view; callers should prefer the deep methods below. */
2345
- readonly contribution: ResponsesImageGenerationContribution;
2346
- inspectRequest(input: ResponsesImageInspectionInput): ResponsesImageAdmission;
2347
- validateSelection(admission: ResponsesImageAdmission, selection: ResponsesHostedToolSelection): void;
2348
- openRequest(input: HostedImageOpenRequestInput): Promise<ResponsesImageRequestScope>;
2349
- release(): Promise<void>;
2350
- }
2351
- interface HostedImageRuntimePolicy {
2352
- readonly providerId: string;
2353
- readonly imageModel: string;
2354
- readonly referenceTtlMs: number;
2355
- readonly maxOutputBytes: number;
2356
- readonly maxTotalOutputBytes: number;
2357
- readonly preferredAccountId?: string;
2358
- readonly preferredAccountGroup?: string;
2359
- readonly boundAccountFallbackPolicy?: 'strict' | 'pool';
2441
+ type DaemonImagePathArea = 'temporary' | 'artifacts' | 'state' | 'evidence' | 'mountManifest';
2442
+ interface DaemonImagePaths {
2443
+ readonly applicationDataRoot: string;
2444
+ readonly imagesRoot: string;
2445
+ readonly temporaryRoot: string;
2446
+ readonly durableRoot: string;
2447
+ readonly artifactsRoot: string;
2448
+ readonly stateRoot: string;
2449
+ readonly evidenceRoot: string;
2450
+ readonly mountManifestRoot: string;
2451
+ readonly mountManifestPath: string;
2360
2452
  }
2361
- interface HostedImageOpenRequestInput {
2362
- readonly admission: ResponsesImageAdmission;
2363
- readonly tenantId: string;
2364
- readonly requestId: string;
2365
- readonly sessionKey: string;
2366
- readonly signal: AbortSignal;
2367
- readonly authorizedPreviousResponseId?: string;
2368
- /** Trusted affinity fact forwarded unchanged into the contribution scope. */
2369
- readonly authorizedPreviousResponseKnownEmpty?: boolean;
2370
- readonly mainProviderId: string;
2371
- readonly selectedMainAccountId?: string;
2453
+ interface ImageRootValidationOptions {
2454
+ readonly label?: string;
2455
+ readonly processDirectory?: string;
2456
+ readonly userHome?: string;
2457
+ readonly temporaryDirectory?: string;
2372
2458
  }
2373
- /** Dormant integration seam for a later Native Responses owner. */
2374
- interface HostedImageContributionFactory {
2375
- acquire(): Promise<HostedImageRuntimeGenerationLease>;
2459
+ interface CreateDaemonImagePathResolverOptions extends ImageRootValidationOptions {
2460
+ readonly configPath: string;
2461
+ readonly storageRoot?: string;
2376
2462
  }
2377
- /** Bind a stable factory to one app-session runtime manager without acquiring. */
2378
- declare function createHostedImageContributionFactory(manager: ImageRuntimeManager): HostedImageContributionFactory;
2379
- interface ImageRuntimeGenerationStatus {
2380
- readonly generationId: string;
2381
- readonly enabled: boolean;
2382
- readonly httpLeases: number;
2383
- readonly hostedLeases: number;
2463
+ interface VerifiedDaemonImagePath {
2464
+ readonly area: DaemonImagePathArea;
2465
+ readonly absolutePath: string;
2466
+ readonly kind: 'opaque-file' | 'opaque-directory' | 'mount-manifest';
2384
2467
  }
2385
- interface ImageRuntimeManagerStatus {
2386
- readonly disposed: boolean;
2387
- readonly current: ImageRuntimeGenerationStatus;
2388
- readonly draining: readonly ImageRuntimeGenerationStatus[];
2468
+ /**
2469
+ * Owns all daemon Images filesystem names. Callers receive opaque capabilities,
2470
+ * never a filename-accepting delete primitive; destructive methods revalidate
2471
+ * the root identity, descendant relationship, basename, and symlink state.
2472
+ */
2473
+ declare class DaemonImagePathResolver {
2474
+ #private;
2475
+ readonly paths: DaemonImagePaths;
2476
+ constructor(options: CreateDaemonImagePathResolverOptions);
2477
+ createOpaqueFile(area: Exclude<DaemonImagePathArea, 'temporary' | 'mountManifest'>, format?: 'bin' | 'json' | 'tmp'): VerifiedDaemonImagePath;
2478
+ createOpaqueDirectory(area?: Exclude<DaemonImagePathArea, 'mountManifest'>): VerifiedDaemonImagePath;
2479
+ mountManifest(): VerifiedDaemonImagePath;
2480
+ /** Revalidate and return one internal root for store-local bounded I/O. */
2481
+ verifiedRoot(area: DaemonImagePathArea): string;
2482
+ /** Revalidate immediately before unlinking a resolver-issued file capability. */
2483
+ removeFile(target: VerifiedDaemonImagePath): void;
2484
+ /** Only empty opaque directories may be removed until the owned-marker layer is composed. */
2485
+ removeEmptyDirectory(target: VerifiedDaemonImagePath): void;
2486
+ private issue;
2487
+ private verifyDestructiveTarget;
2389
2488
  }
2390
- type ImageRuntimeSafeUnavailableReason = ImageCapabilityUnavailableReason | 'disabled' | 'runtime_unavailable';
2391
2489
 
2392
- interface ImageRuntimeCapabilityInspection {
2393
- readonly generationId: string;
2394
- readonly enabled: boolean;
2395
- readonly available: boolean;
2396
- readonly providerId?: ImageProviderId;
2397
- readonly model?: string;
2398
- readonly reason?: ImageRuntimeSafeUnavailableReason;
2399
- readonly capabilities?: ImageCapabilities;
2400
- /**
2401
- * Route-table keys whose ROUTED provider's fresh capability intersection
2402
- * affirms them (multi-provider-image-generation 6.1: /v1/models lists
2403
- * routed-model × fresh-evidence). Absent on synthetic/legacy generations —
2404
- * `listAvailableModels` then falls back to the single-model shape.
2405
- */
2406
- readonly routedModels?: readonly string[];
2490
+ interface FileCodexImageCapabilityEvidenceManifestOwnerOptions {
2491
+ readonly paths: DaemonImagePathResolver;
2492
+ readonly maxEntries?: number;
2493
+ readonly now?: () => number;
2494
+ readonly random?: (bytes: number) => Buffer;
2495
+ readonly hmacSalt?: Uint8Array;
2496
+ readonly replaceManifest?: (targetPath: string, contents: Uint8Array) => void;
2407
2497
  }
2408
- interface ImageRuntimeResourceStatus {
2409
- readonly queue: Readonly<{
2410
- activeJobs: number;
2411
- waitingJobs: number;
2412
- activeAccounts: number;
2413
- waitingAccounts: number;
2414
- waitingTenants: number;
2415
- maxConcurrentJobsPerAccount: number;
2416
- maxQueuedJobs: number;
2417
- accepting: boolean;
2418
- shuttingDown: boolean;
2419
- }>;
2420
- readonly temporary: Readonly<{
2421
- activeScopes: number;
2422
- totalBytes: number;
2423
- tenantCount: number;
2424
- maxActiveScopes: number;
2425
- maxTotalBytes: number;
2426
- maxTenantBytes: number;
2427
- }>;
2428
- readonly storage: Readonly<{
2429
- mounts: number;
2430
- retiredMounts: number;
2431
- referenceEntries: number;
2432
- referenceBytes: number;
2433
- referenceTombstones: number;
2434
- stateCalls: number;
2435
- stateResponses: number;
2436
- stateTombstones: number;
2437
- pendingReferenceDeletes: number;
2438
- maxReferenceEntries: number;
2439
- maxReferenceBytes: number;
2440
- maxTenantReferenceBytes: number;
2441
- maxStateCalls: number;
2442
- maxStateResponses: number;
2443
- }>;
2498
+ type FileCodexImageCapabilityEvidenceSourceOptions = Readonly<(FileCodexImageCapabilityEvidenceManifestOwnerOptions & {
2499
+ readonly ttlMs: number;
2500
+ }) | {
2501
+ readonly owner: FileCodexImageCapabilityEvidenceManifestOwner;
2502
+ readonly ttlMs: number;
2503
+ }>;
2504
+ interface FileCodexImageCapabilityEvidenceStatus {
2505
+ readonly entries: number;
2506
+ readonly freshEntries: number;
2507
+ readonly staleEntries: number;
2508
+ readonly bytes: number;
2444
2509
  }
2445
- interface PreparedImageRuntimeChange {
2446
- readonly generationId: string;
2447
- publish(): void;
2448
- rollback(): void;
2449
- dispose(): Promise<void>;
2510
+ /** Revision-aware manifest owner shared by runtime generations, doctor, and cleanup. */
2511
+ declare class FileCodexImageCapabilityEvidenceManifestOwner {
2512
+ #private;
2513
+ constructor(options: FileCodexImageCapabilityEvidenceManifestOwnerOptions);
2514
+ createSource(ttlMs: number): FileCodexImageCapabilityEvidenceSource;
2515
+ resolveWithTtl(request: CodexImageCapabilityEvidenceRequest, ttlMs: number): Promise<CodexImageCapabilityEvidence>;
2516
+ recordSuccessfulVerificationWithTtl(observation: CodexImageCapabilityObservation, ttlMs: number): Promise<void>;
2517
+ cleanup(now: number, limit: number): Promise<{
2518
+ readonly entriesRemoved: number;
2519
+ readonly bytesRemoved: number;
2520
+ }>;
2521
+ statusWithTtl(ttlMs: number): FileCodexImageCapabilityEvidenceStatus;
2450
2522
  }
2451
- /** App-session owner for stable forwarders and generation-pinned work. */
2452
- declare class ImageRuntimeManager {
2523
+ /** Immutable TTL view over a revision-aware file-backed evidence manifest owner. */
2524
+ declare class FileCodexImageCapabilityEvidenceSource implements CodexImageCapabilityEvidenceSource {
2453
2525
  #private;
2454
- readonly contributions: ImageApiContributions;
2455
- constructor(initial?: PreparedImageRuntimeGeneration);
2456
- prepare(generation: PreparedImageRuntimeGeneration): PreparedImageRuntimeChange;
2457
- acquireHosted(): Promise<HostedImageRuntimeGenerationLease>;
2458
- inspectCapability(apiKeyId: string): Promise<ImageRuntimeCapabilityInspection>;
2459
- listAvailableModels(apiKeyId: string): Promise<readonly string[]>;
2460
- resourceStatus(): ImageRuntimeResourceStatus | undefined;
2461
- status(): ImageRuntimeManagerStatus;
2462
- dispose(): Promise<void>;
2526
+ constructor(options: FileCodexImageCapabilityEvidenceSourceOptions);
2527
+ createView(ttlMs: number): FileCodexImageCapabilityEvidenceSource;
2528
+ resolve(request: CodexImageCapabilityEvidenceRequest): Promise<CodexImageCapabilityEvidence>;
2529
+ recordSuccessfulVerification(observation: CodexImageCapabilityObservation): Promise<void>;
2530
+ cleanup(now: number, limit: number): Promise<{
2531
+ readonly entriesRemoved: number;
2532
+ readonly bytesRemoved: number;
2533
+ }>;
2534
+ status(): FileCodexImageCapabilityEvidenceStatus;
2535
+ ttlMs(): number;
2536
+ /** Lifecycle-symmetric no-op; physical safety no longer depends on local leases. */
2537
+ dispose(): void;
2463
2538
  }
2464
2539
 
2465
- type SafeProvider = 'codex-subscription' | 'unknown' | 'other';
2466
- type SafeModel = 'gpt-image-2' | 'unknown' | 'other';
2467
- type SafeErrorCode = ImageGenerationErrorCode | 'none' | 'other';
2468
- type SafeCountOption = 'unknown' | '0' | '1' | '2-4' | '5+';
2469
- type SafeBooleanOption = boolean | 'unknown';
2470
- type SafeQuality = 'auto' | 'low' | 'medium' | 'high' | 'unknown' | 'other';
2471
- type SafeBackground = 'auto' | 'opaque' | 'transparent' | 'unknown' | 'other';
2472
- type SafeOutputFormat = 'png' | 'jpeg' | 'webp' | 'unknown' | 'other';
2473
- declare const IMAGE_CONFIGURATION_AUDIT_FIELDS: readonly ["enablement", "provider", "model", "account", "queue", "temporary", "limits", "retention", "storage", "remote", "evidence"];
2474
- type ImageConfigurationAuditField = typeof IMAGE_CONFIGURATION_AUDIT_FIELDS[number];
2475
- /** Values are deliberately limited to safe categories and internal generation ids. */
2476
- interface ImageConfigurationAuditRecord {
2477
- readonly outcome: 'applied';
2478
- readonly fields: readonly ImageConfigurationAuditField[];
2479
- readonly previousGenerationId?: string;
2480
- readonly generationId?: string;
2481
- }
2482
- interface ImageHistogramSnapshot {
2483
- readonly count: number;
2484
- readonly sum: number;
2485
- /** Non-cumulative fixed buckets; `upperBound:null` is the overflow bucket. */
2486
- readonly buckets: readonly {
2487
- readonly upperBound: number | null;
2488
- readonly count: number;
2489
- }[];
2540
+ interface FileImageReferenceStoreLimits {
2541
+ readonly ttlMs: number;
2542
+ readonly maxArtifactBytes: number;
2543
+ readonly maxTotalBytes: number;
2544
+ readonly maxTenantBytes: number;
2545
+ readonly maxEntries: number;
2546
+ readonly maxTombstones: number;
2547
+ readonly tombstoneTtlMs: number;
2490
2548
  }
2491
- interface ImageApiMetricDimensions {
2492
- readonly endpoint: 'images.generate' | 'images.edit';
2493
- readonly provider: SafeProvider;
2494
- readonly model: SafeModel;
2495
- readonly action: 'generate' | 'edit';
2496
- readonly quality: SafeQuality;
2497
- readonly background: SafeBackground;
2498
- readonly outputFormat: SafeOutputFormat;
2499
- readonly streaming: SafeBooleanOption;
2500
- readonly requestedOutputs: SafeCountOption;
2501
- readonly partialImages: SafeCountOption;
2502
- readonly terminal: 'completed' | 'failed' | 'cancelled';
2503
- readonly errorCode: SafeErrorCode;
2549
+ interface FileImageReferenceStoreOptions {
2550
+ readonly paths: DaemonImagePathResolver;
2551
+ readonly limits: FileImageReferenceStoreLimits;
2552
+ readonly secretBox?: SecretBox;
2553
+ readonly now?: () => number;
2554
+ readonly random?: (bytes: number) => Buffer;
2555
+ readonly replaceManifest?: (targetPath: string, contents: Uint8Array) => void;
2504
2556
  }
2505
- interface ImageExecutionMetricDimensions {
2506
- readonly provider: SafeProvider;
2507
- readonly model: SafeModel;
2508
- readonly action: 'generate' | 'edit' | 'other';
2509
- readonly quality: SafeQuality;
2510
- readonly background: SafeBackground;
2511
- readonly outputFormat: SafeOutputFormat;
2512
- readonly streaming: SafeBooleanOption;
2513
- readonly requestedOutputs: SafeCountOption;
2514
- readonly terminal: 'completed' | 'failed' | 'cancelled' | 'other';
2515
- readonly errorCode: SafeErrorCode;
2557
+ interface FileImageReferenceReconciliationResult {
2558
+ readonly metadataRemoved: number;
2559
+ readonly metadataDegradedToProviderReference: number;
2560
+ readonly orphanFilesRemoved: number;
2561
+ readonly incompleteFilesRemoved: number;
2562
+ readonly invalidDescendants: number;
2516
2563
  }
2517
- interface ImageApiMetricSnapshot {
2518
- readonly dimensions: ImageApiMetricDimensions;
2519
- readonly requests: number;
2520
- readonly inputCount?: ImageHistogramSnapshot;
2521
- readonly inputBytes?: ImageHistogramSnapshot;
2522
- readonly referenceOutcomes: Readonly<{
2523
- hits: number;
2524
- notFound: number;
2525
- expired: number;
2526
- failed: number;
2527
- }>;
2528
- readonly cleanupOutcomes: Readonly<{
2529
- completed: number;
2530
- failed: number;
2531
- }>;
2564
+ declare class FileImageReferenceStore implements ImageReferenceStore {
2565
+ #private;
2566
+ constructor(options: FileImageReferenceStoreOptions);
2567
+ save(input: ImageReferenceSaveInput): Promise<ImageReferenceMetadata>;
2568
+ /** Generation-bound write entry point; reads and maintenance remain shared. */
2569
+ saveWithLimits(input: ImageReferenceSaveInput, limits: FileImageReferenceStoreLimits): Promise<ImageReferenceMetadata>;
2570
+ /** Updates only app-session maintenance policy; pinned writes pass their own limits. */
2571
+ updateMaintenanceLimits(limits: FileImageReferenceStoreLimits): void;
2572
+ resolve(tenantId: string, referenceId: ImageReferenceId): Promise<ImageReferenceResolution>;
2573
+ delete(tenantId: string, referenceId: ImageReferenceId): Promise<boolean>;
2574
+ /** Daemon-internal cleanup path; accepts only the local reference-domain tenant HMAC. */
2575
+ deleteByHashedTenantKey(tenantKey: string, referenceId: ImageReferenceId): Promise<boolean>;
2576
+ cleanup(now?: number): Promise<number>;
2577
+ status(): {
2578
+ readonly entries: number;
2579
+ readonly bytes: number;
2580
+ readonly tombstones: number;
2581
+ };
2582
+ hasLiveReferenceByHashedTenantKey(tenantKey: string, referenceId: ImageReferenceId, now?: number): Promise<boolean>;
2583
+ reconcileOwnedFiles(maxEntries: number): Promise<FileImageReferenceReconciliationResult>;
2584
+ openArtifact(fileName: string, byteLength: number, signal?: AbortSignal): Promise<ReadableStream<Uint8Array>>;
2585
+ private exclusive;
2586
+ private tenantKey;
2587
+ private newReferenceId;
2588
+ private validateSaveInput;
2589
+ private selectVictims;
2590
+ private nextTombstones;
2591
+ private writeArtifact;
2592
+ private artifactPath;
2593
+ private validArtifact;
2594
+ private removeArtifact;
2595
+ private safeUnlinkArtifactPath;
2596
+ private releaseLease;
2597
+ private manifestPath;
2598
+ private persist;
2599
+ private atomicReplace;
2600
+ private loadManifest;
2532
2601
  }
2533
- interface ImageExecutionMetricSnapshot {
2534
- readonly dimensions: ImageExecutionMetricDimensions;
2535
- readonly executions: number;
2536
- readonly finalLatencyMs: ImageHistogramSnapshot;
2537
- readonly queueWaitMs?: ImageHistogramSnapshot;
2538
- readonly generationDurationMs?: ImageHistogramSnapshot;
2539
- readonly firstPartialLatencyMs?: ImageHistogramSnapshot;
2540
- readonly inputCount: ImageHistogramSnapshot;
2541
- readonly inputBytes: ImageHistogramSnapshot;
2542
- readonly outputCount: ImageHistogramSnapshot;
2543
- readonly outputBytes: ImageHistogramSnapshot;
2544
- readonly retryCount?: ImageHistogramSnapshot;
2545
- readonly authRefreshCount?: ImageHistogramSnapshot;
2546
- readonly referenceSaveCount?: ImageHistogramSnapshot;
2547
- readonly retentionRollbackFailures?: ImageHistogramSnapshot;
2602
+
2603
+ interface FileResponsesImageStateStoreLimits {
2604
+ readonly maxCalls: number;
2605
+ readonly maxResponses: number;
2606
+ readonly maxTombstones: number;
2607
+ readonly tombstoneTtlMs: number;
2548
2608
  }
2549
- interface ImageObservabilitySnapshot {
2550
- readonly apiRequests: readonly ImageApiMetricSnapshot[];
2551
- readonly executions: readonly ImageExecutionMetricSnapshot[];
2552
- readonly configurationChanges: readonly ImageConfigurationAuditRecord[];
2553
- readonly overflow: Readonly<{
2554
- apiRecords: number;
2555
- telemetryRecords: number;
2556
- }>;
2609
+ interface FileResponsesImageStateStoreOptions {
2610
+ readonly paths: DaemonImagePathResolver;
2611
+ readonly limits: FileResponsesImageStateStoreLimits;
2612
+ readonly now?: () => number;
2613
+ readonly random?: (bytes: number) => Buffer;
2614
+ readonly replaceManifest?: (targetPath: string, contents: Uint8Array) => void;
2557
2615
  }
2558
- interface ImageObservabilityOptions {
2559
- /** Hard cap applied independently to API and execution dimension maps. */
2560
- readonly maxDimensionSets?: number;
2616
+ interface PendingResponsesImageReferenceDelete {
2617
+ readonly referenceTenantKey: string;
2618
+ readonly binding: ResponsesImageCallBinding;
2561
2619
  }
2562
- /** Process-local, metadata-only aggregation for HTTP and hosted Images work. */
2563
- declare class ImageObservability {
2620
+ /** Durable production implementation of the existing Responses image-state contract. */
2621
+ declare class FileResponsesImageStateStore implements ResponsesImageStateStore {
2564
2622
  #private;
2565
- readonly telemetrySink: ImageTelemetrySink;
2566
- readonly audit: (record: ImageApiAuditRecord) => void;
2567
- constructor(options?: ImageObservabilityOptions);
2568
- recordApiAudit(record: ImageApiAuditRecord): void;
2569
- recordTelemetry(record: ImageTelemetryRecord): void;
2570
- recordConfigurationAudit(record: ImageConfigurationAuditRecord): void;
2571
- snapshot(): ImageObservabilitySnapshot;
2572
- reset(): void;
2623
+ constructor(options: FileResponsesImageStateStoreOptions);
2624
+ commit(input: ResponsesImageStateCommitInput): Promise<readonly ResponsesImageCallBinding[]>;
2625
+ /** Generation-bound write entry point; reads and maintenance remain shared. */
2626
+ commitWithLimits(input: ResponsesImageStateCommitInput, limits: FileResponsesImageStateStoreLimits): Promise<readonly ResponsesImageCallBinding[]>;
2627
+ /** Updates only app-session maintenance policy; pinned commits pass their own limits. */
2628
+ updateMaintenanceLimits(limits: FileResponsesImageStateStoreLimits): void;
2629
+ resolveCall(tenantId: string, callId: ResponsesImageCallId): Promise<ResponsesImageCallResolution>;
2630
+ resolveResponse(tenantId: string, responseId: string): Promise<ResponsesImageResponseResolution>;
2631
+ deleteCall(tenantId: string, callId: ResponsesImageCallId): Promise<ResponsesImageCallBinding | undefined>;
2632
+ deleteResponse(tenantId: string, responseId: string): Promise<boolean>;
2633
+ cleanup(now?: number): Promise<readonly ResponsesImageCallBinding[]>;
2634
+ pendingReferenceDeletes(limit?: number): readonly PendingResponsesImageReferenceDelete[];
2635
+ acknowledgeReferenceDeletes(completed: readonly PendingResponsesImageReferenceDelete[]): Promise<number>;
2636
+ reconcileBrokenReferenceLinks(hasLiveReference: (referenceTenantKey: string, referenceId: ResponsesImageCallBinding['referenceId']) => Promise<boolean>, maxEntries: number): Promise<readonly ResponsesImageCallBinding[]>;
2637
+ status(): {
2638
+ readonly calls: number;
2639
+ readonly responses: number;
2640
+ readonly tombstones: number;
2641
+ readonly pendingReferenceDeletes: number;
2642
+ };
2643
+ private exclusive;
2644
+ private failure;
2645
+ private assertCommit;
2646
+ private tenantKey;
2647
+ private rememberTombstone;
2648
+ private enqueuePendingReferenceDelete;
2649
+ private hasTombstone;
2650
+ private prunedTombstones;
2651
+ private pruneTombstonesInPlace;
2652
+ private sameTombstones;
2653
+ private touch;
2654
+ private releaseCall;
2655
+ private releaseResponse;
2656
+ private manifestPath;
2657
+ private persist;
2658
+ private atomicReplace;
2659
+ private loadManifest;
2573
2660
  }
2574
2661
 
2575
- interface PreparedServerConfigChange {
2576
- /** Publish the prepared snapshot. Implementations should make this an infallible swap. */
2577
- publish(): void | Promise<void>;
2578
- /** Restore the exact runtime snapshot that preceded publish. */
2579
- rollback(): void | Promise<void>;
2580
- /** Release an unpublished or rolled-back replacement. */
2581
- dispose(): void | Promise<void>;
2662
+ interface ImageStorageMountBackend {
2663
+ readonly id: string;
2664
+ readonly createdAt: number;
2665
+ readonly resolver: DaemonImagePathResolver;
2666
+ readonly references: FileImageReferenceStore;
2667
+ readonly responsesState: FileResponsesImageStateStore;
2582
2668
  }
2583
-
2584
- /**
2585
- * autoDisableStore.ts — the daemon's PROCESS-IN-MEMORY auto-disable store.
2586
- *
2587
- * A DB-backed embedder can persist 401/403 auto-disable durably so a UI can
2588
- * render per-key health. The daemon has no DB, and its only
2589
- * durable layer is `config.json` — but writing auto-disable back there in v1
2590
- * would (1) cause write amplification under a 401 storm and (2) collide with
2591
- * the at-rest encryption schema that owns the `apiKeys[]` on-disk
2592
- * format. So v1 records auto-disable IN MEMORY only:
2593
- * - `markAutoDisabled(keyId, status, at)` records `{ status, at, reason }`,
2594
- * - `isDisabled(keyId)` / `get(keyId)` read it back,
2595
- * - `loadPoolKeys` reads this store and flips a flagged key's `enabled` to
2596
- * `false`, so `getAvailableKeys` skips it within this process lifetime.
2597
- *
2598
- * Restart resets the store (the honest v1 boundary — see spec). Persistence
2599
- * (encrypted write-back) is a child-3 follow-up.
2600
- *
2601
- * @module @omnicross/daemon/pool/autoDisableStore
2602
- */
2603
- /** One in-memory auto-disable record for a pool key. */
2604
- interface AutoDisableRecord {
2605
- /** The HTTP status that triggered the disable (401/403). */
2606
- status: number;
2607
- /** Epoch-ms when the disable was recorded. */
2608
- at: number;
2609
- /** Always `'auth_failure'` in v1 (the only auto-disable trigger). */
2610
- reason: 'auth_failure';
2669
+ interface ImageStorageMountCatalogOptions {
2670
+ readonly pathOptions: Omit<CreateDaemonImagePathResolverOptions, 'storageRoot'>;
2671
+ readonly activeStorageRoot?: string;
2672
+ readonly referenceLimits: FileImageReferenceStoreLimits;
2673
+ readonly responsesStateLimits: FileResponsesImageStateStoreLimits;
2674
+ readonly secretBox?: SecretBox;
2675
+ readonly now?: () => number;
2676
+ readonly random?: (bytes: number) => Buffer;
2677
+ readonly replaceCatalog?: (targetPath: string, contents: Uint8Array) => void;
2678
+ readonly reconcileCorruptManifests?: boolean;
2611
2679
  }
2612
- /**
2613
- * A process-lifetime in-memory store of auto-disabled pool keys, keyed by the
2614
- * pool key id. Constructed once in `buildDaemon` and injected as the pool's
2615
- * `disableKey` / `markAutoDisabled` sinks AND read by `loadPoolKeys`.
2616
- */
2617
- declare class AutoDisableStore {
2618
- private readonly records;
2619
- /** Record (or overwrite) an auth-failure auto-disable for `keyId`. */
2620
- markAutoDisabled(keyId: string, status: number, at: number): void;
2621
- /** Whether `keyId` is currently auto-disabled in this process. */
2622
- isDisabled(keyId: string): boolean;
2623
- /** Read the auto-disable record for `keyId`, or `undefined` when healthy. */
2624
- get(keyId: string): AutoDisableRecord | undefined;
2625
- /** Clear all records (tests / teardown). */
2626
- clear(): void;
2680
+ interface ImageStorageMountPolicy {
2681
+ readonly referenceLimits: FileImageReferenceStoreLimits;
2682
+ readonly responsesStateLimits: FileResponsesImageStateStoreLimits;
2627
2683
  }
2628
-
2629
- /**
2630
- * ConfigFileProviderConfigSource — the daemon's file-backed `ProviderConfigSource`
2631
- * port impl: an embedder of `@omnicross/core`'s provider catalog port.
2632
- *
2633
- * The daemon's `config.json` provider rows ARE the catalog. Of the port's ten
2634
- * methods, FOUR are real (the ones the BYO proxy/outbound path actually hits):
2635
- * - `getProvider(id)` — map a `DaemonProviderConfig` row to an `LLMProvider`.
2636
- * - `getTransformerService()` — a single `TransformerService` seeded by
2637
- * `registerBuiltinTransformers` in the ctor.
2638
- * - `getMainTransformer(id)` — the transformer for the provider's target wire
2639
- * format (anthropic → AnthropicTransformer, gemini → GeminiTransformer,
2640
- * openai → null/identity), mirroring the host `AgentModelsManager` switch.
2641
- * `resolveProviderChain` unshifts this FORMAT-FIRST into the provider chain.
2642
- * - `resolveTransformerChain(id, model)` — the provider's CUSTOM
2643
- * `transformer.use[]` chain (app-parity-2 child 2: ENFORCED). The format
2644
- * transformer is NOT included here (getMainTransformer supplies it,
2645
- * format-first); no `transformer.use[]` → empty chain.
2646
- *
2647
- * The remaining SIX are minimal sensible stubs (never hit on the BYO single-key
2648
- * path — the boot smoke test is the proof).
2649
- *
2650
- * @module @omnicross/daemon/ports/ConfigFileProviderConfigSource
2651
- */
2652
-
2653
- declare class ConfigFileProviderConfigSource implements ProviderConfigSource {
2654
- private readonly providers;
2655
- private readonly transformerService;
2656
- /**
2657
- * Optional reload-hook (key-pool change, design D4). A no-type-coupling
2658
- * callback invoked at the END of `reload(...)`. `buildDaemon` injects
2659
- * `() => pool.invalidateCache()` so the `ApiKeyPoolService.keyCache` is
2660
- * flushed after a hot-reload swaps the catalog — WITHOUT this port ever
2661
- * importing/depending on `ApiKeyPoolService`. Absent = no-op (single-key
2662
- * boots that never construct a pool stay byte-identical).
2663
- */
2664
- private reloadHook;
2665
- constructor(config: DaemonConfig);
2666
- /**
2667
- * Register a callback fired after every `reload(...)`. Used by `buildDaemon`
2668
- * to invalidate the pool's keyCache on a hot-reload. The port stays ignorant
2669
- * of what the callback does (no pool type dependency).
2670
- */
2671
- setReloadHook(fn: () => void): void;
2672
- /**
2673
- * Read the live (post-reload) provider row for `providerId`, or `undefined`.
2674
- * Exposed so the pool's `loadKeys` reads the SAME live catalog Map this port
2675
- * serves (so a hot-reload is observed on the next load after `invalidateCache`).
2676
- */
2677
- getProviderRow(providerId: string): DaemonProviderConfig | undefined;
2678
- /** Await the built-in transformer registration (tests await this before dispatch). */
2679
- ready(): Promise<void>;
2680
- /**
2681
- * Replace the live provider catalog in place (additive — does NOT touch the
2682
- * ten port methods, the seeded `TransformerService`, or `ready()`). Called by
2683
- * the admin API after a provider POST/PUT/DELETE persists `config.json`, so the
2684
- * next outbound request sees the new catalog WITHOUT a daemon restart. The Map
2685
- * swap is synchronous; an in-flight request keeps its already-resolved
2686
- * provider (no locking needed for a single-operator local daemon).
2687
- */
2688
- reload(config: DaemonConfig): void;
2689
- /** Clear + repopulate the private providers Map from a fresh provider list. */
2690
- setProviders(providers: readonly DaemonProviderConfig[]): void;
2691
- getProvider(id: string): Promise<LLMProvider | null>;
2692
- getTransformerService(): TransformerService | undefined;
2693
- getMainTransformer(providerId: string): Promise<Transformer | null>;
2694
- resolveTransformerChain(providerId: string, _model?: string): Promise<ResolvedTransformerChain>;
2695
- resolveRoutedModel(): Promise<null>;
2696
- resolveEffectiveModels(): Promise<{
2697
- background?: string;
2698
- vision?: string;
2699
- }>;
2700
- getAgentDefaultModels(): Promise<AgentDefaultModels>;
2701
- hasVisionCapability(): Promise<boolean>;
2702
- getGlobalModelParameters(): Promise<GlobalModelParameters>;
2703
- getDiscoveredModelMaxTokens(): Promise<number | undefined>;
2684
+ interface PreparedImageStorageMountActivation {
2685
+ readonly backend: ImageStorageMountBackend;
2686
+ publish(): ImageStorageMountBackend;
2687
+ rollback(): void;
2688
+ dispose(): void;
2704
2689
  }
2705
-
2706
- /**
2707
- * JsonApiServerSettingsStore — the daemon's file-backed `ApiServerSettingsStore`
2708
- * port impl.
2709
- *
2710
- * The serving core persists the outbound-API server config (`{ enabled,
2711
- * networkBinding, endpoints, port }`) under a SINGLE settings key
2712
- * (`OUTBOUND_API_SERVER_CONFIG_KEY === 'outboundApiServer.config'`). Here that
2713
- * store is the daemon's `config.json` `server`
2714
- * field. `loadServerConfig(store)` / `saveServerConfig(store, cfg)` (core)
2715
- * normalize + persist through this 2-method surface.
2716
- *
2717
- * Only the one outbound-API key is ever read/written — any other key is a no-op
2718
- * miss (returns `undefined`) so the surface stays honest about what it backs.
2719
- *
2720
- * @module @omnicross/daemon/ports/JsonApiServerSettingsStore
2721
- */
2722
-
2723
- interface JsonSettingsDocumentSnapshot {
2724
- readonly existed: boolean;
2725
- /** Raw persisted bytes; may contain encrypted secrets and must never enter a DTO or log. */
2726
- readonly bytes?: Uint8Array;
2690
+ /** Owns the durable-root set independently from any one runtime generation. */
2691
+ declare class ImageStorageMountCatalog {
2692
+ #private;
2693
+ constructor(options: ImageStorageMountCatalogOptions);
2694
+ active(): ImageStorageMountBackend;
2695
+ mountsForRead(): readonly ImageStorageMountBackend[];
2696
+ status(): {
2697
+ readonly mounts: number;
2698
+ readonly retiredMounts: number;
2699
+ };
2700
+ startupReconciliationStatus(): {
2701
+ readonly corruptManifestsQuarantined: number;
2702
+ };
2703
+ utilization(): {
2704
+ readonly referenceEntries: number;
2705
+ readonly referenceBytes: number;
2706
+ readonly referenceTombstones: number;
2707
+ readonly stateCalls: number;
2708
+ readonly stateResponses: number;
2709
+ readonly stateTombstones: number;
2710
+ readonly pendingReferenceDeletes: number;
2711
+ };
2712
+ /** Pins a backend object until its owning runtime generation drains. */
2713
+ retainBackend(backend: ImageStorageMountBackend): () => void;
2714
+ activate(storageRoot?: string): ImageStorageMountBackend;
2715
+ /** Prepare a validated backend without changing the catalog's active mount. */
2716
+ prepareActivation(storageRoot?: string, policy?: ImageStorageMountPolicy): PreparedImageStorageMountActivation;
2717
+ retireEmptyMount(mountId: string): boolean;
2718
+ private createResolver;
2719
+ private createBackend;
2720
+ private applyMaintenancePolicy;
2721
+ private newMountId;
2722
+ private isManifestError;
2723
+ private quarantineManifest;
2724
+ private isVerifiedEmpty;
2725
+ private catalogPath;
2726
+ private persist;
2727
+ private atomicReplace;
2728
+ private loadCatalog;
2727
2729
  }
2728
- type AtomicDocumentReplace = (targetPath: string, contents: Uint8Array) => void;
2729
- declare class JsonApiServerSettingsStore implements ApiServerSettingsStore {
2730
- private readonly configPath;
2731
- private readonly box;
2732
- private readonly atomicReplace;
2733
- /**
2734
- * @param configPath the daemon config.json whose `server` field is backed.
2735
- * @param box OPTIONAL at-rest `SecretBox` (upstream-proxy). When set, the
2736
- * `server.proxy.*` passwords are encrypted-on-`set` /
2737
- * decrypted-on-`get` (the settings-store path is otherwise not
2738
- * secret-aware — every OTHER server field is non-secret). Null
2739
- * ⇒ passthrough (legacy/pure tests unchanged).
2740
- */
2741
- constructor(configPath: string, box?: SecretBox | null, atomicReplace?: AtomicDocumentReplace);
2742
- get<T = unknown>(key: string): Promise<T | undefined>;
2743
- set<T = unknown>(key: string, value: T): Promise<void>;
2744
- /** Capture the exact prior document for an admin transaction rollback. */
2745
- captureDocumentSnapshot(): JsonSettingsDocumentSnapshot;
2746
- /** Restore exact prior bytes (including unrelated fields and encrypted secrets). */
2747
- restoreDocumentSnapshot(snapshot: JsonSettingsDocumentSnapshot): void;
2748
- /** Encrypt the proxy passwords + webhook + billing secrets before persisting (no-op without a box). */
2749
- private encryptSecrets;
2750
- /** Decrypt the proxy passwords + webhook + billing secrets on read (no-op without a box). */
2751
- private decryptSecrets;
2752
- /** Read the config.json, tolerating a missing/corrupt file (→ empty shape). */
2753
- private readFile;
2730
+ declare class MountedImageReferenceStore implements ImageReferenceStore {
2731
+ private readonly catalog;
2732
+ private readonly writeBackend?;
2733
+ private readonly writeLimits?;
2734
+ constructor(catalog: ImageStorageMountCatalog, writeBackend?: ImageStorageMountBackend | undefined, writeLimits?: FileImageReferenceStoreLimits | undefined);
2735
+ bindWriteBackend(backend: ImageStorageMountBackend, limits: FileImageReferenceStoreLimits): MountedImageReferenceStore;
2736
+ status(): Readonly<{
2737
+ referenceEntries: number;
2738
+ referenceBytes: number;
2739
+ referenceTombstones: number;
2740
+ stateCalls: number;
2741
+ stateResponses: number;
2742
+ stateTombstones: number;
2743
+ pendingReferenceDeletes: number;
2744
+ mounts: number;
2745
+ retiredMounts: number;
2746
+ }>;
2747
+ save(input: ImageReferenceSaveInput): Promise<_omnicross_contracts_image_generation_types.ImageReferenceMetadata>;
2748
+ resolve(tenantId: string, referenceId: ImageReferenceId): Promise<ImageReferenceResolution>;
2749
+ delete(tenantId: string, referenceId: ImageReferenceId): Promise<boolean>;
2750
+ deleteByHashedTenantKey(tenantKey: string, referenceId: ImageReferenceId): Promise<boolean>;
2751
+ cleanup(now?: number): Promise<number>;
2752
+ }
2753
+ declare class MountedResponsesImageStateStore implements ResponsesImageStateStore {
2754
+ private readonly catalog;
2755
+ private readonly writeBackend?;
2756
+ private readonly writeLimits?;
2757
+ constructor(catalog: ImageStorageMountCatalog, writeBackend?: ImageStorageMountBackend | undefined, writeLimits?: FileResponsesImageStateStoreLimits | undefined);
2758
+ bindWriteBackend(backend: ImageStorageMountBackend, limits: FileResponsesImageStateStoreLimits): MountedResponsesImageStateStore;
2759
+ commit(input: ResponsesImageStateCommitInput): Promise<readonly ResponsesImageCallBinding[]>;
2760
+ resolveCall(tenantId: string, callId: ResponsesImageCallId): Promise<ResponsesImageCallResolution>;
2761
+ resolveResponse(tenantId: string, responseId: string): Promise<ResponsesImageResponseResolution>;
2762
+ deleteCall(tenantId: string, callId: ResponsesImageCallId): Promise<ResponsesImageCallBinding | undefined>;
2763
+ deleteResponse(tenantId: string, responseId: string): Promise<boolean>;
2764
+ cleanup(now?: number): Promise<readonly ResponsesImageCallBinding[]>;
2754
2765
  }
2755
2766
 
2756
- /**
2757
- * JsonPricingStore — the daemon's file-backed `PricingStore` port impl.
2758
- *
2759
- * Durable storage for the model pricing table, backed by a pretty-printed json
2760
- * file (a sibling of `config.json`, `pricing.json` by convention) holding a
2761
- * `PricingEntry[]`. Reads tolerate a missing/corrupt file (→ empty table);
2762
- * every mutation rewrites the full array (the table is at most a few thousand
2763
- * rows same trade-off as `JsonOutboundKeyDb`). The table starts EMPTY: no
2764
- * seeding — the first pricing-source refresh (or a manual upsert) populates it.
2765
- *
2766
- * Beyond the core port, the store exposes a STORE-LOCAL `delete` (the port is
2767
- * frozen; the admin DELETE route calls the concrete class and then invalidates
2768
- * the engine cache).
2769
- *
2770
- * @module @omnicross/daemon/ports/JsonPricingStore
2771
- */
2772
-
2773
- declare class JsonPricingStore implements PricingStore {
2774
- private readonly pricingPath;
2775
- constructor(pricingPath: string);
2776
- /**
2777
- * Return whether the durable snapshot can actually serve at least one price.
2778
- *
2779
- * This intentionally checks the file itself instead of relying on refresh
2780
- * metadata: a recent `lastSuccessAt` must not hide a deleted, truncated, or
2781
- * otherwise unusable pricing table after a crash or manual file edit.
2782
- */
2783
- hasUsableSnapshot(): boolean;
2784
- getAll(): Promise<PricingEntry[]>;
2785
- /**
2786
- * Insert or update one row keyed (providerId, modelId). `asUserEdit` stamps
2787
- * user provenance (source 'user', userEdited, editedAt now) so the row is
2788
- * protected from auto-overwrite during source refreshes; a non-user upsert
2789
- * stamps source 'litellm' and clears nothing it should not (a plain source
2790
- * upsert through this method overwrites the row wholesale).
2791
- */
2792
- upsert(input: PricingEntryInput, asUserEdit: boolean): Promise<PricingEntry>;
2793
- /**
2794
- * Apply a batch fetched from a pricing source. Rows whose local copy is
2795
- * user-edited are NOT applied — they come back as `{ current, incoming }`
2796
- * conflicts; everything else is upserted with the supplied automatic source.
2797
- * ONE file write for the whole batch.
2798
- */
2799
- bulkApplyFromSource(entries: PricingEntryInput[], source?: AutomaticPricingSource): Promise<{
2800
- applied: PricingEntry[];
2801
- conflicts: Array<{
2802
- current: PricingEntry;
2803
- incoming: PricingEntryInput;
2804
- }>;
2767
+ interface ImageDoctorLocalSnapshot {
2768
+ readonly config: Readonly<{
2769
+ enabled: boolean;
2770
+ provider: ImageProviderId;
2771
+ model: string;
2772
+ valid: boolean;
2773
+ errorCount: number;
2774
+ /** Distinct providers the routing table names (doctor per-provider rows). */
2775
+ routedProviders: readonly ImageProviderId[];
2776
+ }>;
2777
+ readonly roots: Readonly<{
2778
+ valid: boolean;
2779
+ verifiedAreas: number;
2780
+ expectedAreas: number;
2781
+ }>;
2782
+ readonly stores: Readonly<{
2783
+ valid: boolean;
2784
+ mounts: number;
2785
+ retiredMounts: number;
2786
+ referenceEntries: number;
2787
+ referenceBytes: number;
2788
+ stateCalls: number;
2789
+ stateResponses: number;
2790
+ corruptManifestsQuarantined: number;
2791
+ }>;
2792
+ readonly permissions: Readonly<{
2793
+ valid: boolean;
2794
+ rows: number;
2795
+ legacyRows: number;
2796
+ invalidRows: number;
2797
+ imagesAuthorizedRows: number;
2798
+ }>;
2799
+ /** The Codex-subscription account backing codex-routed image models. */
2800
+ readonly account: Readonly<{
2801
+ present: boolean;
2802
+ usable: boolean;
2803
+ reason: 'ready' | 'missing' | 'unavailable';
2804
+ }>;
2805
+ /** The Antigravity-subscription account backing NanoBanana image models. */
2806
+ readonly antigravityAccount: Readonly<{
2807
+ present: boolean;
2808
+ usable: boolean;
2809
+ reason: 'ready' | 'missing' | 'unavailable';
2810
+ }>;
2811
+ readonly evidence: Readonly<FileCodexImageCapabilityEvidenceStatus & {
2812
+ valid: boolean;
2805
2813
  }>;
2806
- /**
2807
- * Apply per-row conflict decisions: 'overwrite' replaces the local row with
2808
- * the incoming values (clearing the user-edited mark), 'skip' counts only.
2809
- */
2810
- applyResolutions(resolutions: Array<{
2811
- incoming: PricingEntryInput;
2812
- action: 'overwrite' | 'skip';
2813
- }>): Promise<PricingResolution>;
2814
- /**
2815
- * STORE-LOCAL (not on the core port): remove one row. Returns whether a row
2816
- * was actually removed. The admin DELETE handler calls this then invalidates
2817
- * the engine cache.
2818
- */
2819
- delete(providerId: string, modelId: string): Promise<boolean>;
2820
- /** Upsert into `rows` IN PLACE (no write) and return the resulting entry. */
2821
- private applyUpsert;
2822
- /** Read the pricing rows, tolerating a missing/corrupt file (→ empty list). */
2823
- private readRows;
2824
- private writeRows;
2825
- /** Isolated for deterministic failure testing; never removes the target. */
2826
- private replaceFile;
2827
2814
  }
2828
-
2829
- interface CodexAuthHelperConfig {
2830
- command: string;
2831
- args: string[];
2815
+ type ImageDoctorLiveFailureCode = 'images_disabled' | 'codex_account_unavailable' | 'evidence_store_unavailable' | 'evidence_persist_failed' | ImageGenerationErrorCode;
2816
+ type ImageDoctorLiveResult = Readonly<{
2817
+ ok: true;
2818
+ code: 'verified';
2819
+ model: 'gpt-image-2';
2820
+ quality: 'low';
2821
+ outputFormat: 'png';
2822
+ freshEvidenceEntries: number;
2823
+ }> | Readonly<{
2824
+ ok: false;
2825
+ code: ImageDoctorLiveFailureCode;
2826
+ }>;
2827
+ interface ImageDoctorService {
2828
+ inspectLocal(config: ImagesServerConfig): Promise<ImageDoctorLocalSnapshot>;
2829
+ verifyLive(config: ImagesServerConfig, signal: AbortSignal): Promise<ImageDoctorLiveResult>;
2832
2830
  }
2833
2831
 
2834
- type IntegrationClientId = 'codex' | 'claude';
2835
- type IntegrationKeyOwnership = 'managed' | 'selected';
2836
- /** Secret-free pointer to an access key. The plaintext remains in the encrypted key store. */
2837
- interface IntegrationKeyBinding {
2838
- keyId: string;
2839
- ownership: IntegrationKeyOwnership;
2840
- }
2841
- /** Redacted access-key state exposed by the integrations admin API. */
2842
- interface IntegrationKeyBindingStatus {
2843
- id: string;
2844
- name: string;
2845
- keyPrefix: string;
2846
- ownership: IntegrationKeyOwnership;
2847
- revealable: boolean;
2848
- enabled: boolean;
2849
- revoked: boolean;
2850
- allowedEndpoints: OutboundPermission[];
2851
- requiredEndpoints: OutboundPermission[];
2852
- loopbackOnly: boolean;
2853
- }
2854
- type IntegrationStatusKind = 'not-installed' | 'enabled' | 'configuration-drift' | 'configuration-missing' | 'key-missing';
2855
- interface IntegrationClientStatus {
2856
- client: IntegrationClientId;
2857
- status: IntegrationStatusKind;
2858
- configPath: string;
2859
- installedAt?: number;
2860
- gatewayBaseUrl?: string;
2861
- message?: string;
2862
- /** Selected key metadata only; never contains plaintext or the encrypted envelope. */
2863
- key?: IntegrationKeyBindingStatus;
2832
+ type PreparedImageRuntimeGeneration = {
2833
+ readonly id: string;
2834
+ readonly enabled: true;
2835
+ readonly imageApi: ImageApiContributions;
2836
+ readonly hosted: ResponsesImageGenerationContribution;
2837
+ readonly hostedRuntime: HostedImageRuntimePolicy;
2838
+ readonly inspectCapability?: (apiKeyId: string) => Promise<Omit<ImageRuntimeCapabilityInspection, 'generationId'>>;
2839
+ readonly readRuntimeStatus?: () => ImageRuntimeResourceStatus;
2840
+ dispose(): void | Promise<void>;
2841
+ } | {
2842
+ readonly id: string;
2843
+ readonly enabled: false;
2844
+ dispose(): void | Promise<void>;
2845
+ };
2846
+ interface HostedImageRuntimeGenerationLease {
2847
+ readonly generationId: string;
2848
+ /** Compatibility/debug view; callers should prefer the deep methods below. */
2849
+ readonly contribution: ResponsesImageGenerationContribution;
2850
+ inspectRequest(input: ResponsesImageInspectionInput): ResponsesImageAdmission;
2851
+ validateSelection(admission: ResponsesImageAdmission, selection: ResponsesHostedToolSelection): void;
2852
+ openRequest(input: HostedImageOpenRequestInput): Promise<ResponsesImageRequestScope>;
2853
+ release(): Promise<void>;
2864
2854
  }
2865
- interface IntegrationChangePlan {
2866
- client: IntegrationClientId;
2867
- configPath: string;
2868
- action: 'install' | 'none' | 'repair';
2869
- canApply: boolean;
2870
- /** Redacted logical fields only; never file contents or credential values. */
2871
- changes: string[];
2872
- warnings: string[];
2855
+ interface HostedImageRuntimePolicy {
2856
+ readonly providerId: string;
2857
+ readonly imageModel: string;
2858
+ readonly referenceTtlMs: number;
2859
+ readonly maxOutputBytes: number;
2860
+ readonly maxTotalOutputBytes: number;
2861
+ readonly preferredAccountId?: string;
2862
+ readonly preferredAccountGroup?: string;
2863
+ readonly boundAccountFallbackPolicy?: 'strict' | 'pool';
2873
2864
  }
2874
- interface IntegrationInstallRecord {
2875
- client: IntegrationClientId;
2876
- configPath: string;
2877
- originalExisted: boolean;
2878
- /** Encrypted by IntegrationStateStore before it reaches disk. */
2879
- originalContent: string;
2880
- originalHash: string;
2881
- installedHash: string;
2882
- installedAt: number;
2883
- gatewayBaseUrl: string;
2884
- /** Codex auth.json snapshot and installed hash; absent on legacy records. */
2885
- credentialFile?: IntegrationManagedFileRecord;
2865
+ interface HostedImageOpenRequestInput {
2866
+ readonly admission: ResponsesImageAdmission;
2867
+ readonly tenantId: string;
2868
+ readonly requestId: string;
2869
+ readonly sessionKey: string;
2870
+ readonly signal: AbortSignal;
2871
+ readonly authorizedPreviousResponseId?: string;
2872
+ /** Trusted affinity fact forwarded unchanged into the contribution scope. */
2873
+ readonly authorizedPreviousResponseKnownEmpty?: boolean;
2874
+ readonly mainProviderId: string;
2875
+ readonly selectedMainAccountId?: string;
2886
2876
  }
2887
- interface IntegrationManagedFileRecord {
2888
- path: string;
2889
- originalExisted: boolean;
2890
- /** Encrypted by IntegrationStateStore before it reaches disk. */
2891
- originalContent: string;
2892
- originalHash: string;
2893
- installedHash: string;
2877
+ /** Dormant integration seam for a later Native Responses owner. */
2878
+ interface HostedImageContributionFactory {
2879
+ acquire(): Promise<HostedImageRuntimeGenerationLease>;
2894
2880
  }
2895
- interface IntegrationGatewayKeyRecord {
2896
- id: string;
2897
- /** Encrypted by IntegrationStateStore before it reaches disk. */
2898
- secret: string;
2899
- createdAt: number;
2881
+ /** Bind a stable factory to one app-session runtime manager without acquiring. */
2882
+ declare function createHostedImageContributionFactory(manager: ImageRuntimeManager): HostedImageContributionFactory;
2883
+ interface ImageRuntimeGenerationStatus {
2884
+ readonly generationId: string;
2885
+ readonly enabled: boolean;
2886
+ readonly httpLeases: number;
2887
+ readonly hostedLeases: number;
2900
2888
  }
2901
- interface IntegrationState {
2902
- version: 1;
2903
- /** Legacy shared-key layout. New installs use `keyBindings`; retained for safe migration. */
2904
- gatewayKey?: IntegrationGatewayKeyRecord;
2905
- keyBindings?: Partial<Record<IntegrationClientId, IntegrationKeyBinding>>;
2906
- clients: Partial<Record<IntegrationClientId, IntegrationInstallRecord>>;
2889
+ interface ImageRuntimeManagerStatus {
2890
+ readonly disposed: boolean;
2891
+ readonly current: ImageRuntimeGenerationStatus;
2892
+ readonly draining: readonly ImageRuntimeGenerationStatus[];
2907
2893
  }
2894
+ type ImageRuntimeSafeUnavailableReason = ImageCapabilityUnavailableReason | 'disabled' | 'runtime_unavailable';
2908
2895
 
2909
- /** Encrypted, Omnicross-owned state for reversible native CLI configuration. */
2910
- declare class IntegrationStateStore {
2911
- readonly path: string;
2912
- private readonly box;
2913
- constructor(path: string, box: SecretBox);
2914
- load(): IntegrationState;
2915
- save(state: IntegrationState): void;
2896
+ interface ImageRuntimeCapabilityInspection {
2897
+ readonly generationId: string;
2898
+ readonly enabled: boolean;
2899
+ readonly available: boolean;
2900
+ readonly providerId?: ImageProviderId;
2901
+ readonly model?: string;
2902
+ readonly reason?: ImageRuntimeSafeUnavailableReason;
2903
+ readonly capabilities?: ImageCapabilities;
2904
+ /**
2905
+ * Route-table keys whose ROUTED provider's fresh capability intersection
2906
+ * affirms them (multi-provider-image-generation 6.1: /v1/models lists
2907
+ * routed-model × fresh-evidence). Absent on synthetic/legacy generations —
2908
+ * `listAvailableModels` then falls back to the single-model shape.
2909
+ */
2910
+ readonly routedModels?: readonly string[];
2911
+ /**
2912
+ * One row per provider named by the routing table (images-settings-tab D2):
2913
+ * availability with a safe reason, the models its own fresh evidence
2914
+ * affirms, and the capability values for evidence-age projection. Absent on
2915
+ * synthetic/legacy generations.
2916
+ */
2917
+ readonly providers?: readonly ImageRuntimeProviderInspection[];
2916
2918
  }
2917
-
2918
- interface IntegrationManagerOptions {
2919
- configPath: string;
2920
- gatewayBaseUrl: string;
2921
- keyDb: OutboundKeyDb;
2922
- stateStore: IntegrationStateStore;
2923
- codexAuthHelper?: CodexAuthHelperConfig;
2924
- homeDir?: string;
2919
+ /** Per-provider capability inspection row (images-settings-tab D2). */
2920
+ interface ImageRuntimeProviderInspection {
2921
+ readonly providerId: ImageProviderId;
2922
+ readonly available: boolean;
2923
+ readonly reason?: ImageRuntimeSafeUnavailableReason;
2924
+ /** This provider's route keys affirmed by its fresh evidence intersection. */
2925
+ readonly models: readonly string[];
2926
+ readonly capabilities?: ImageCapabilities;
2925
2927
  }
2926
- /** Coordinates per-client least-privilege keys with reversible native CLI config edits. */
2927
- declare class IntegrationManager {
2928
- private readonly options;
2929
- private readonly homeDir;
2930
- private readonly codexAuthHelper;
2931
- constructor(options: IntegrationManagerOptions);
2932
- listStatus(): Promise<IntegrationClientStatus[]>;
2933
- plan(client: IntegrationClientId, configPath?: string): Promise<IntegrationChangePlan>;
2934
- install(client: IntegrationClientId, configPath?: string): Promise<IntegrationClientStatus>;
2935
- repair(client: IntegrationClientId): Promise<IntegrationClientStatus>;
2936
- remove(client: IntegrationClientId): Promise<IntegrationClientStatus>;
2937
- /** Bind a user-confirmed access key and grant only this client's required endpoints. */
2938
- bindIntegrationKey(client: IntegrationClientId, keyId: string): Promise<IntegrationClientStatus>;
2939
- /** Rotate every Omnicross-managed client binding; user-selected keys remain untouched. */
2940
- rotateGatewayKey(): Promise<{
2941
- keyIds: Partial<Record<IntegrationClientId, string>>;
2928
+ interface ImageRuntimeResourceStatus {
2929
+ readonly queue: Readonly<{
2930
+ activeJobs: number;
2931
+ waitingJobs: number;
2932
+ activeAccounts: number;
2933
+ waitingAccounts: number;
2934
+ waitingTenants: number;
2935
+ maxConcurrentJobsPerAccount: number;
2936
+ maxQueuedJobs: number;
2937
+ accepting: boolean;
2938
+ shuttingDown: boolean;
2942
2939
  }>;
2943
- /** Resolve the plaintext only for the command-auth helper; callers must not log it. */
2944
- getIntegrationToken(client: IntegrationClientId): Promise<string>;
2945
- /** Compatibility alias for callers predating per-client bindings. */
2946
- getGatewayToken(client?: IntegrationClientId): Promise<string>;
2947
- private ensureClientKey;
2948
- private createManagedClientKey;
2949
- private installedSecret;
2950
- private rebindInstalledClient;
2951
- private statusFor;
2952
- private boundKeyDetails;
2953
- private retireManagedKeys;
2954
- private defaultConfigPath;
2955
- private renderInstalled;
2940
+ readonly temporary: Readonly<{
2941
+ activeScopes: number;
2942
+ totalBytes: number;
2943
+ tenantCount: number;
2944
+ maxActiveScopes: number;
2945
+ maxTotalBytes: number;
2946
+ maxTenantBytes: number;
2947
+ }>;
2948
+ readonly storage: Readonly<{
2949
+ mounts: number;
2950
+ retiredMounts: number;
2951
+ referenceEntries: number;
2952
+ referenceBytes: number;
2953
+ referenceTombstones: number;
2954
+ stateCalls: number;
2955
+ stateResponses: number;
2956
+ stateTombstones: number;
2957
+ pendingReferenceDeletes: number;
2958
+ maxReferenceEntries: number;
2959
+ maxReferenceBytes: number;
2960
+ maxTenantReferenceBytes: number;
2961
+ maxStateCalls: number;
2962
+ maxStateResponses: number;
2963
+ }>;
2964
+ }
2965
+ interface PreparedImageRuntimeChange {
2966
+ readonly generationId: string;
2967
+ publish(): void;
2968
+ rollback(): void;
2969
+ dispose(): Promise<void>;
2970
+ }
2971
+ /** App-session owner for stable forwarders and generation-pinned work. */
2972
+ declare class ImageRuntimeManager {
2973
+ #private;
2974
+ readonly contributions: ImageApiContributions;
2975
+ constructor(initial?: PreparedImageRuntimeGeneration);
2976
+ prepare(generation: PreparedImageRuntimeGeneration): PreparedImageRuntimeChange;
2977
+ acquireHosted(): Promise<HostedImageRuntimeGenerationLease>;
2978
+ inspectCapability(apiKeyId: string): Promise<ImageRuntimeCapabilityInspection>;
2979
+ listAvailableModels(apiKeyId: string): Promise<readonly string[]>;
2980
+ resourceStatus(): ImageRuntimeResourceStatus | undefined;
2981
+ status(): ImageRuntimeManagerStatus;
2982
+ dispose(): Promise<void>;
2956
2983
  }
2957
2984
 
2958
- /**
2959
- * accountsAntigravityOAuth the daemon admin API's ANTIGRAVITY interactive
2960
- * OAuth path (`POST /accounts/antigravity/oauth/start` +
2961
- * `GET /accounts/antigravity/oauth/:sessionId/status`).
2962
- *
2963
- * Modeled on the codex loopback flow (`accountsCodexOAuth`): antigravity's
2964
- * redirect is a FIXED loopback `http://127.0.0.1:51121/oauth-callback`, so the
2965
- * flow is ASYNC + POLLED `start` arms the one-shot loopback listener, kicks
2966
- * the capture→exchange→email→project-handshake→persist chain off ASYNC, and
2967
- * returns ONLY `{ authUrl, sessionId }` (public — client_id + state). The app
2968
- * opens `authUrl`; the browser redirects to the loopback; the daemon captures
2969
- * the `code`, validates `state`, exchanges it, resolves the userinfo email and
2970
- * the Code Assist project (the antigravity dialect of the shared resolver),
2971
- * and persists the minted token through the encrypted credential store. The
2972
- * app POLLS `status` until `done`/`error`.
2973
- *
2974
- * SECRET SPINE (same invariant as codex/grok): the minted access/refresh token
2975
- * NEVER crosses to the client — it lands ONLY in the encrypted store. The
2976
- * poll `status` body is TOKEN-FREE (`{ state, message? }`). Port 51121 is a
2977
- * single resource → only ONE antigravity sign-in may be in flight (409).
2978
- *
2979
- * REUSES the `@omnicross/subscriptions` antigravity flow + the CLI's
2980
- * `awaitLoopbackCode` listener (parameterized binding) — it does NOT rebuild
2981
- * the authorize/exchange/handshake logic.
2982
- *
2983
- * @module @omnicross/daemon/admin/accountsAntigravityOAuth
2984
- */
2985
+ type SafeProvider = 'codex-subscription' | 'unknown' | 'other';
2986
+ type SafeModel = 'gpt-image-2' | 'unknown' | 'other';
2987
+ type SafeErrorCode = ImageGenerationErrorCode | 'none' | 'other';
2988
+ type SafeCountOption = 'unknown' | '0' | '1' | '2-4' | '5+';
2989
+ type SafeBooleanOption = boolean | 'unknown';
2990
+ type SafeQuality = 'auto' | 'low' | 'medium' | 'high' | 'unknown' | 'other';
2991
+ type SafeBackground = 'auto' | 'opaque' | 'transparent' | 'unknown' | 'other';
2992
+ type SafeOutputFormat = 'png' | 'jpeg' | 'webp' | 'unknown' | 'other';
2993
+ declare const IMAGE_CONFIGURATION_AUDIT_FIELDS: readonly ["enablement", "provider", "model", "account", "queue", "temporary", "limits", "retention", "storage", "remote", "evidence"];
2994
+ type ImageConfigurationAuditField = typeof IMAGE_CONFIGURATION_AUDIT_FIELDS[number];
2995
+ /** Values are deliberately limited to safe categories and internal generation ids. */
2996
+ interface ImageConfigurationAuditRecord {
2997
+ readonly outcome: 'applied';
2998
+ readonly fields: readonly ImageConfigurationAuditField[];
2999
+ readonly previousGenerationId?: string;
3000
+ readonly generationId?: string;
3001
+ }
3002
+ interface ImageHistogramSnapshot {
3003
+ readonly count: number;
3004
+ readonly sum: number;
3005
+ /** Non-cumulative fixed buckets; `upperBound:null` is the overflow bucket. */
3006
+ readonly buckets: readonly {
3007
+ readonly upperBound: number | null;
3008
+ readonly count: number;
3009
+ }[];
3010
+ }
3011
+ interface ImageApiMetricDimensions {
3012
+ readonly endpoint: 'images.generate' | 'images.edit';
3013
+ readonly provider: SafeProvider;
3014
+ readonly model: SafeModel;
3015
+ readonly action: 'generate' | 'edit';
3016
+ readonly quality: SafeQuality;
3017
+ readonly background: SafeBackground;
3018
+ readonly outputFormat: SafeOutputFormat;
3019
+ readonly streaming: SafeBooleanOption;
3020
+ readonly requestedOutputs: SafeCountOption;
3021
+ readonly partialImages: SafeCountOption;
3022
+ readonly terminal: 'completed' | 'failed' | 'cancelled';
3023
+ readonly errorCode: SafeErrorCode;
3024
+ }
3025
+ interface ImageExecutionMetricDimensions {
3026
+ readonly provider: SafeProvider;
3027
+ readonly model: SafeModel;
3028
+ readonly action: 'generate' | 'edit' | 'other';
3029
+ readonly quality: SafeQuality;
3030
+ readonly background: SafeBackground;
3031
+ readonly outputFormat: SafeOutputFormat;
3032
+ readonly streaming: SafeBooleanOption;
3033
+ readonly requestedOutputs: SafeCountOption;
3034
+ readonly terminal: 'completed' | 'failed' | 'cancelled' | 'other';
3035
+ readonly errorCode: SafeErrorCode;
3036
+ }
3037
+ interface ImageApiMetricSnapshot {
3038
+ readonly dimensions: ImageApiMetricDimensions;
3039
+ readonly requests: number;
3040
+ readonly inputCount?: ImageHistogramSnapshot;
3041
+ readonly inputBytes?: ImageHistogramSnapshot;
3042
+ readonly referenceOutcomes: Readonly<{
3043
+ hits: number;
3044
+ notFound: number;
3045
+ expired: number;
3046
+ failed: number;
3047
+ }>;
3048
+ readonly cleanupOutcomes: Readonly<{
3049
+ completed: number;
3050
+ failed: number;
3051
+ }>;
3052
+ }
3053
+ interface ImageExecutionMetricSnapshot {
3054
+ readonly dimensions: ImageExecutionMetricDimensions;
3055
+ readonly executions: number;
3056
+ readonly finalLatencyMs: ImageHistogramSnapshot;
3057
+ readonly queueWaitMs?: ImageHistogramSnapshot;
3058
+ readonly generationDurationMs?: ImageHistogramSnapshot;
3059
+ readonly firstPartialLatencyMs?: ImageHistogramSnapshot;
3060
+ readonly inputCount: ImageHistogramSnapshot;
3061
+ readonly inputBytes: ImageHistogramSnapshot;
3062
+ readonly outputCount: ImageHistogramSnapshot;
3063
+ readonly outputBytes: ImageHistogramSnapshot;
3064
+ readonly retryCount?: ImageHistogramSnapshot;
3065
+ readonly authRefreshCount?: ImageHistogramSnapshot;
3066
+ readonly referenceSaveCount?: ImageHistogramSnapshot;
3067
+ readonly retentionRollbackFailures?: ImageHistogramSnapshot;
3068
+ }
3069
+ interface ImageObservabilitySnapshot {
3070
+ readonly apiRequests: readonly ImageApiMetricSnapshot[];
3071
+ readonly executions: readonly ImageExecutionMetricSnapshot[];
3072
+ readonly configurationChanges: readonly ImageConfigurationAuditRecord[];
3073
+ readonly overflow: Readonly<{
3074
+ apiRecords: number;
3075
+ telemetryRecords: number;
3076
+ }>;
3077
+ }
3078
+ interface ImageObservabilityOptions {
3079
+ /** Hard cap applied independently to API and execution dimension maps. */
3080
+ readonly maxDimensionSets?: number;
3081
+ }
3082
+ /** Process-local, metadata-only aggregation for HTTP and hosted Images work. */
3083
+ declare class ImageObservability {
3084
+ #private;
3085
+ readonly telemetrySink: ImageTelemetrySink;
3086
+ readonly audit: (record: ImageApiAuditRecord) => void;
3087
+ constructor(options?: ImageObservabilityOptions);
3088
+ recordApiAudit(record: ImageApiAuditRecord): void;
3089
+ recordTelemetry(record: ImageTelemetryRecord): void;
3090
+ recordConfigurationAudit(record: ImageConfigurationAuditRecord): void;
3091
+ snapshot(): ImageObservabilitySnapshot;
3092
+ reset(): void;
3093
+ }
2985
3094
 
2986
- /** The loopback-listener fn (injected so tests need not bind a real port). */
2987
- type AntigravityLoopbackFn = (state: string, timeoutMs?: number, signal?: AbortSignal) => Promise<string>;
3095
+ interface PreparedServerConfigChange {
3096
+ /** Publish the prepared snapshot. Implementations should make this an infallible swap. */
3097
+ publish(): void | Promise<void>;
3098
+ /** Restore the exact runtime snapshot that preceded publish. */
3099
+ rollback(): void | Promise<void>;
3100
+ /** Release an unpublished or rolled-back replacement. */
3101
+ dispose(): void | Promise<void>;
3102
+ }
2988
3103
 
2989
3104
  /**
2990
- * cliLaunch — the admin API's "launch a coding CLI in a terminal, pointed at the
2991
- * daemon" surface (dashboard parity with the desktop app's Code CLI tab).
3105
+ * autoDisableStore.ts — the daemon's PROCESS-IN-MEMORY auto-disable store.
2992
3106
  *
2993
- * This is the EXTERNAL-terminal analogue of `commands/launch.ts`: it reuses the
2994
- * same `@omnicross/cli-launcher` builders (which register one route on the
2995
- * RESIDENT `ProviderProxy` and return the redirect env `ANTHROPIC_BASE_URL` +
2996
- * a one-shot ROUTE token, codex's `-c base_url=…` overrides, etc.), then opens a
2997
- * NEW terminal window running the CLI with that env injected. The route token —
2998
- * NOT an upstream credential is the only secret in the env; it is removed when
2999
- * the session is stopped (`onSessionEnd`).
3107
+ * A DB-backed embedder can persist 401/403 auto-disable durably so a UI can
3108
+ * render per-key health. The daemon has no DB, and its only
3109
+ * durable layer is `config.json` but writing auto-disable back there in v1
3110
+ * would (1) cause write amplification under a 401 storm and (2) collide with
3111
+ * the at-rest encryption schema that owns the `apiKeys[]` on-disk
3112
+ * format. So v1 records auto-disable IN MEMORY only:
3113
+ * - `markAutoDisabled(keyId, status, at)` records `{ status, at, reason }`,
3114
+ * - `isDisabled(keyId)` / `get(keyId)` read it back,
3115
+ * - `loadPoolKeys` reads this store and flips a flagged key's `enabled` to
3116
+ * `false`, so `getAvailableKeys` skips it within this process lifetime.
3000
3117
  *
3001
- * SECRET DISCIPLINE: the env carries a route token (proxy-scoped, revocable),
3002
- * never a provider key. On win32 the token rides the spawned process environment
3003
- * (inherited by the terminal), never the command line / a file on disk.
3118
+ * Restart resets the store (the honest v1 boundary see spec). Persistence
3119
+ * (encrypted write-back) is a child-3 follow-up.
3004
3120
  *
3005
- * @module @omnicross/daemon/admin/cliLaunch
3121
+ * @module @omnicross/daemon/pool/autoDisableStore
3006
3122
  */
3007
-
3008
- /** Injectable PATH probe (tests stub this; default scans `process.env.PATH`). */
3009
- type PathProbe = (candidate: string) => string | null;
3010
- /** Open a NEW terminal window running `command [extraArgs…]` with `env` injected. */
3011
- type TerminalCleanup = () => void;
3012
- type TerminalOpener = (input: {
3013
- cli: string;
3014
- command: string;
3015
- extraArgs: string[];
3016
- env: Record<string, string>;
3017
- cwd?: string;
3018
- platform: NodeJS.Platform;
3019
- onFailure?: () => void;
3020
- }) => void | TerminalCleanup;
3123
+ /** One in-memory auto-disable record for a pool key. */
3124
+ interface AutoDisableRecord {
3125
+ /** The HTTP status that triggered the disable (401/403). */
3126
+ status: number;
3127
+ /** Epoch-ms when the disable was recorded. */
3128
+ at: number;
3129
+ /** Always `'auth_failure'` in v1 (the only auto-disable trigger). */
3130
+ reason: 'auth_failure';
3131
+ }
3021
3132
  /**
3022
- * Injectable shell runner for `POST /cli/:cli/install` (tests stub this; the
3023
- * default execs the install command with a bounded timeout). Returns the host's
3024
- * honest install outcome — `error` carries stderr/the failure reason.
3133
+ * A process-lifetime in-memory store of auto-disabled pool keys, keyed by the
3134
+ * pool key id. Constructed once in `buildDaemon` and injected as the pool's
3135
+ * `disableKey` / `markAutoDisabled` sinks AND read by `loadPoolKeys`.
3025
3136
  */
3026
- type CommandRunner = (command: string) => Promise<{
3027
- ok: boolean;
3028
- error?: string;
3029
- }>;
3137
+ declare class AutoDisableStore {
3138
+ private readonly records;
3139
+ /** Record (or overwrite) an auth-failure auto-disable for `keyId`. */
3140
+ markAutoDisabled(keyId: string, status: number, at: number): void;
3141
+ /** Whether `keyId` is currently auto-disabled in this process. */
3142
+ isDisabled(keyId: string): boolean;
3143
+ /** Read the auto-disable record for `keyId`, or `undefined` when healthy. */
3144
+ get(keyId: string): AutoDisableRecord | undefined;
3145
+ /** Clear all records (tests / teardown). */
3146
+ clear(): void;
3147
+ }
3030
3148
 
3031
3149
  /**
3032
- * searchAdminApi — the admin API's search surface (search-settings-ui D3 +
3033
- * search-settings-tab D4).
3034
- *
3035
- * Three routes over the daemon's search state, dispatched from `adminApi.ts`'s
3036
- * `case 'search'`:
3037
- *
3038
- * - `GET /admin/api/search/diagnostics` — a READ-ONLY, secret-free, network-free
3039
- * snapshot: one row per provider the daemon can run (the ONE runtime's
3040
- * descriptors, plus `unconfigured` rows for known API providers the persisted
3041
- * config does not name — the doctor's classification), the effective frontend
3042
- * modes, and the explicit apply semantics (codex immediate, rest restart).
3043
- * - `POST /admin/api/search/test { providerId }` — ONE live fixed-query check on
3044
- * a configured provider, classified by the doctor's pure functions. The
3045
- * machine-facing health probe: it sends exactly `SEARCH_DOCTOR_QUERY`, never a
3046
- * caller-supplied query, and never returns result content (plan §11.3 — its
3047
- * contract is the automated doctor's fixed-query discipline).
3048
- * - `POST /admin/api/search/query { providerId, query }` — the INTERACTIVE
3049
- * channel for the settings page's per-provider test panel (owner feedback
3050
- * 2026-09-02): ONE operator-typed query through ONE provider's contribution
3051
- * built from the PERSISTED config, returning the doctor-classified diagnostic
3052
- * PLUS the sanitized results. The two disciplines stay separate routes on
3053
- * purpose: bending `/test` to accept a query would erase the boundary its
3054
- * pinned tests and consumers depend on.
3150
+ * ConfigFileProviderConfigSource — the daemon's file-backed `ProviderConfigSource`
3151
+ * port impl: an embedder of `@omnicross/core`'s provider catalog port.
3055
3152
  *
3056
- * SECRET SPINE (all three routes): no response ever carries a configured VALUE,
3057
- * and a failure response carries only the doctor's SANITIZED error shape — raw
3058
- * upstream error bodies (which may quote the stored key) never serialize. The
3059
- * query endpoint additionally sanitizes every returned result field BEFORE
3060
- * serialization (plan §11.1: search results are untrusted input), and the
3061
- * operator's query is never logged anywhere.
3153
+ * The daemon's `config.json` provider rows ARE the catalog. Of the port's ten
3154
+ * methods, FOUR are real (the ones the BYO proxy/outbound path actually hits):
3155
+ * - `getProvider(id)` map a `DaemonProviderConfig` row to an `LLMProvider`.
3156
+ * - `getTransformerService()` a single `TransformerService` seeded by
3157
+ * `registerBuiltinTransformers` in the ctor.
3158
+ * - `getMainTransformer(id)` the transformer for the provider's target wire
3159
+ * format (anthropic → AnthropicTransformer, gemini → GeminiTransformer,
3160
+ * openai → null/identity), mirroring the host `AgentModelsManager` switch.
3161
+ * `resolveProviderChain` unshifts this FORMAT-FIRST into the provider chain.
3162
+ * - `resolveTransformerChain(id, model)` — the provider's CUSTOM
3163
+ * `transformer.use[]` chain (app-parity-2 child 2: ENFORCED). The format
3164
+ * transformer is NOT included here (getMainTransformer supplies it,
3165
+ * format-first); no `transformer.use[]` → empty chain.
3062
3166
  *
3063
- * The diagnostics dep is OPTIONAL (`AdminApiDeps.searchStatus`): light embedders
3064
- * that wire no search runtime get 501 for all routes (the voucher/allowance
3065
- * optionality precedent) rather than a fabricated snapshot.
3167
+ * The remaining SIX are minimal sensible stubs (never hit on the BYO single-key
3168
+ * path the boot smoke test is the proof).
3066
3169
  *
3067
- * @module @omnicross/daemon/admin/searchAdminApi
3170
+ * @module @omnicross/daemon/ports/ConfigFileProviderConfigSource
3068
3171
  */
3069
3172
 
3070
- /**
3071
- * The daemon search state the admin surface needs. Structurally satisfied by
3072
- * what `bootstrap.ts` already holds (the ONE runtime + its captured modes);
3073
- * `testFetch` is a TEST SEAM so route tests can intercept the one live probe
3074
- * without any network.
3075
- */
3076
- interface SearchAdminRuntimeStatus {
3077
- /** The daemon's ONE assembled search runtime (provider descriptors). */
3078
- readonly runtime: SearchRuntime;
3079
- /** Modes as captured at bootstrap (responses/anthropic are these, live). */
3080
- readonly modes: SearchFrontendModes;
3081
- /** TEST SEAM: fetch primitive for the live-test probe. Absent ⇒ real transport. */
3082
- readonly testFetch?: (url: string, init: RequestInit) => Promise<Response>;
3173
+ declare class ConfigFileProviderConfigSource implements ProviderConfigSource {
3174
+ private readonly providers;
3175
+ private readonly transformerService;
3176
+ /**
3177
+ * Optional reload-hook (key-pool change, design D4). A no-type-coupling
3178
+ * callback invoked at the END of `reload(...)`. `buildDaemon` injects
3179
+ * `() => pool.invalidateCache()` so the `ApiKeyPoolService.keyCache` is
3180
+ * flushed after a hot-reload swaps the catalog WITHOUT this port ever
3181
+ * importing/depending on `ApiKeyPoolService`. Absent = no-op (single-key
3182
+ * boots that never construct a pool stay byte-identical).
3183
+ */
3184
+ private reloadHook;
3185
+ constructor(config: DaemonConfig);
3186
+ /**
3187
+ * Register a callback fired after every `reload(...)`. Used by `buildDaemon`
3188
+ * to invalidate the pool's keyCache on a hot-reload. The port stays ignorant
3189
+ * of what the callback does (no pool type dependency).
3190
+ */
3191
+ setReloadHook(fn: () => void): void;
3192
+ /**
3193
+ * Read the live (post-reload) provider row for `providerId`, or `undefined`.
3194
+ * Exposed so the pool's `loadKeys` reads the SAME live catalog Map this port
3195
+ * serves (so a hot-reload is observed on the next load after `invalidateCache`).
3196
+ */
3197
+ getProviderRow(providerId: string): DaemonProviderConfig | undefined;
3198
+ /** Await the built-in transformer registration (tests await this before dispatch). */
3199
+ ready(): Promise<void>;
3200
+ /**
3201
+ * Replace the live provider catalog in place (additive — does NOT touch the
3202
+ * ten port methods, the seeded `TransformerService`, or `ready()`). Called by
3203
+ * the admin API after a provider POST/PUT/DELETE persists `config.json`, so the
3204
+ * next outbound request sees the new catalog WITHOUT a daemon restart. The Map
3205
+ * swap is synchronous; an in-flight request keeps its already-resolved
3206
+ * provider (no locking needed for a single-operator local daemon).
3207
+ */
3208
+ reload(config: DaemonConfig): void;
3209
+ /** Clear + repopulate the private providers Map from a fresh provider list. */
3210
+ setProviders(providers: readonly DaemonProviderConfig[]): void;
3211
+ getProvider(id: string): Promise<LLMProvider | null>;
3212
+ getTransformerService(): TransformerService | undefined;
3213
+ getMainTransformer(providerId: string): Promise<Transformer | null>;
3214
+ resolveTransformerChain(providerId: string, _model?: string): Promise<ResolvedTransformerChain>;
3215
+ resolveRoutedModel(): Promise<null>;
3216
+ resolveEffectiveModels(): Promise<{
3217
+ background?: string;
3218
+ vision?: string;
3219
+ }>;
3220
+ getAgentDefaultModels(): Promise<AgentDefaultModels>;
3221
+ hasVisionCapability(): Promise<boolean>;
3222
+ getGlobalModelParameters(): Promise<GlobalModelParameters>;
3223
+ getDiscoveredModelMaxTokens(): Promise<number | undefined>;
3083
3224
  }
3084
3225
 
3085
3226
  /**
3086
- * migration.ts — the export gather + import apply logic for the passphrase pack
3087
- * (app-parity child 6, design D2/D3/D5).
3088
- *
3089
- * EXPORT (`gatherExport`): read the FULL local state DECRYPTED in-memory — every
3090
- * provider row (scalars / modelConfigs / single apiKey / pool apiKeys /
3091
- * transformer) via `loadConfig` (the at-rest box decrypts on read) AND the
3092
- * subscription tokens via `credentialStore.getFullConfig()` (also decrypted) —
3093
- * serialize to ONE bundle JSON, and `sealPack` it under the passphrase-derived
3094
- * key. The caller returns ONLY the opaque pack; the decrypted bundle + the
3095
- * passphrase live only in local variables and are never logged.
3227
+ * JsonApiServerSettingsStore — the daemon's file-backed `ApiServerSettingsStore`
3228
+ * port impl.
3096
3229
  *
3097
- * IMPORT (`applyImport`): `openPack` decrypts + authenticates (a wrong passphrase
3098
- * or a tampered pack fails the GCM auth-tag BEFORE any write — atomic). Every
3099
- * provider is re-validated through `parseProviderInput` and every token block
3100
- * through `validateTokenBody` (deny-by-default a malicious blob cannot inject
3101
- * unknown fields or escape the allowlist). Validation collects ALL rows BEFORE
3102
- * applying, so a structurally invalid pack does not leave a half-applied state.
3103
- * Apply merges by provider id (additive default): a new id is added; a colliding
3104
- * id is skipped (or overwritten with `mode:'overwrite'`). Writes go through the
3105
- * EXISTING paths (`saveConfig` re-encrypts at-rest under the LOCAL box +
3106
- * `writeProviderTokens`/`appendProviderAccount`), so imported secrets land
3107
- * `enc:`-encrypted under the TARGET machine's key — the passphrase key is used
3108
- * ONLY for transport.
3230
+ * The serving core persists the outbound-API server config (`{ enabled,
3231
+ * networkBinding, endpoints, port }`) under a SINGLE settings key
3232
+ * (`OUTBOUND_API_SERVER_CONFIG_KEY === 'outboundApiServer.config'`). Here that
3233
+ * store is the daemon's `config.json` `server`
3234
+ * field. `loadServerConfig(store)` / `saveServerConfig(store, cfg)` (core)
3235
+ * normalize + persist through this 2-method surface.
3109
3236
  *
3110
- * SECRET SPINE: the export RESPONSE is the opaque pack ONLY; the import RESPONSE
3111
- * is status-only counts. No decrypted secret + no passphrase ever reaches a
3112
- * response body or a log here.
3237
+ * Only the one outbound-API key is ever read/written any other key is a no-op
3238
+ * miss (returns `undefined`) so the surface stays honest about what it backs.
3113
3239
  *
3114
- * @module @omnicross/daemon/migration/migration
3240
+ * @module @omnicross/daemon/ports/JsonApiServerSettingsStore
3115
3241
  */
3116
3242
 
3117
- /**
3118
- * The credential-store surface the migration paths need: the full DECRYPTED read
3119
- * (export) + the multi-account append (import re-encrypts at-rest). One shape so
3120
- * `ExportDeps` + `ImportDeps` can be unified into `MigrationDeps` without a
3121
- * `credentialStore` type conflict.
3122
- */
3123
- interface MigrationCredentialStore extends SubscriptionAccountAppender {
3124
- getFullConfig(): Promise<AccountTokensConfig>;
3243
+ interface JsonSettingsDocumentSnapshot {
3244
+ readonly existed: boolean;
3245
+ /** Raw persisted bytes; may contain encrypted secrets and must never enter a DTO or log. */
3246
+ readonly bytes?: Uint8Array;
3125
3247
  }
3126
-
3127
- /** Minimal, auth-gated admin API for secret-free account allowance snapshots. */
3128
-
3129
- interface AccountAllowanceAdminReader {
3130
- list(filter?: {
3131
- providerId?: SubscriptionProviderId;
3132
- accountId?: string;
3133
- }): Promise<AccountAllowanceSnapshot[]>;
3134
- refreshClaude(accountId?: string): Promise<AccountAllowanceSnapshot[]>;
3135
- /** Optional: Codex active `/wham/usage` refresh (absent on older daemons). */
3136
- refreshCodex?(accountId?: string): Promise<AccountAllowanceSnapshot[]>;
3137
- /** Optional: Kimi `/coding/v1/usages` refresh (absent on older daemons). */
3138
- refreshKimi?(accountId?: string): Promise<AccountAllowanceSnapshot[]>;
3139
- /** Optional: OpenCodeGo `/v1/usage` refresh (absent on older daemons). */
3140
- refreshOpenCodeGo?(accountId?: string): Promise<AccountAllowanceSnapshot[]>;
3141
- /** Optional: Grok CLI-billing refresh (absent on older daemons). */
3142
- refreshGrok?(accountId?: string): Promise<AccountAllowanceSnapshot[]>;
3143
- /** Optional: Copilot user-quota refresh (absent on older daemons). */
3144
- refreshCopilot?(accountId?: string): Promise<AccountAllowanceSnapshot[]>;
3145
- /** Optional: Gemini Code-Assist quota refresh (absent on older daemons). */
3146
- refreshGemini?(accountId?: string): Promise<AccountAllowanceSnapshot[]>;
3147
- /** Optional: Antigravity quotaSummary refresh (absent on older daemons). */
3148
- refreshAntigravity?(accountId?: string): Promise<AccountAllowanceSnapshot[]>;
3149
- removeAccountSnapshot?(providerId: SubscriptionProviderId, accountId: string): void;
3150
- removeProviderSnapshots?(providerId: SubscriptionProviderId): void;
3151
- getSchedulingStatus?(): AccountAllowanceSchedulingStatus;
3248
+ type AtomicDocumentReplace = (targetPath: string, contents: Uint8Array) => void;
3249
+ declare class JsonApiServerSettingsStore implements ApiServerSettingsStore {
3250
+ private readonly configPath;
3251
+ private readonly box;
3252
+ private readonly atomicReplace;
3253
+ /**
3254
+ * @param configPath the daemon config.json whose `server` field is backed.
3255
+ * @param box OPTIONAL at-rest `SecretBox` (upstream-proxy). When set, the
3256
+ * `server.proxy.*` passwords are encrypted-on-`set` /
3257
+ * decrypted-on-`get` (the settings-store path is otherwise not
3258
+ * secret-aware — every OTHER server field is non-secret). Null
3259
+ * ⇒ passthrough (legacy/pure tests unchanged).
3260
+ */
3261
+ constructor(configPath: string, box?: SecretBox | null, atomicReplace?: AtomicDocumentReplace);
3262
+ get<T = unknown>(key: string): Promise<T | undefined>;
3263
+ set<T = unknown>(key: string, value: T): Promise<void>;
3264
+ /** Capture the exact prior document for an admin transaction rollback. */
3265
+ captureDocumentSnapshot(): JsonSettingsDocumentSnapshot;
3266
+ /** Restore exact prior bytes (including unrelated fields and encrypted secrets). */
3267
+ restoreDocumentSnapshot(snapshot: JsonSettingsDocumentSnapshot): void;
3268
+ /** Encrypt the proxy passwords + webhook + billing secrets before persisting (no-op without a box). */
3269
+ private encryptSecrets;
3270
+ /** Decrypt the proxy passwords + webhook + billing secrets on read (no-op without a box). */
3271
+ private decryptSecrets;
3272
+ /** Read the config.json, tolerating a missing/corrupt file ( empty shape). */
3273
+ private readFile;
3152
3274
  }
3153
3275
 
3154
- /** Token-free subscription account list entry (passthrough from core's service). */
3155
- interface AdminAccountsLister {
3156
- listAll(): Promise<unknown[]>;
3157
- }
3158
- /** One key's live cooldown health (mirrors core `KeyHealthEntry`; read-only). */
3159
- interface PoolKeyHealth {
3160
- until: number;
3161
- errors: number;
3162
- lastStatus: number | null;
3163
- }
3164
- /**
3165
- * The READ-ONLY pool-health surface the admin view needs (key-pool design D7).
3166
- * Structurally satisfied by core's `ApiKeyPoolService.getKeyHealth`; typed as a
3167
- * minimal reader so `adminApi` carries no class coupling and can never reach a
3168
- * key value through it (cooldown health only).
3169
- */
3170
- interface PoolHealthReader {
3171
- getKeyHealth(providerId: string): Promise<Record<string, PoolKeyHealth>>;
3172
- }
3173
3276
  /**
3174
- * The BYO provider-key quota surface the keys view needs structurally
3175
- * satisfied by `ProviderKeyQuotaService`. Read-only; the DTO is secret-free by
3176
- * construction (normalized windows + diagnostic codes only).
3277
+ * JsonPricingStore the daemon's file-backed `PricingStore` port impl.
3278
+ *
3279
+ * Durable storage for the model pricing table, backed by a pretty-printed json
3280
+ * file (a sibling of `config.json`, `pricing.json` by convention) holding a
3281
+ * `PricingEntry[]`. Reads tolerate a missing/corrupt file (→ empty table);
3282
+ * every mutation rewrites the full array (the table is at most a few thousand
3283
+ * rows — same trade-off as `JsonOutboundKeyDb`). The table starts EMPTY: no
3284
+ * seeding — the first pricing-source refresh (or a manual upsert) populates it.
3285
+ *
3286
+ * Beyond the core port, the store exposes a STORE-LOCAL `delete` (the port is
3287
+ * frozen; the admin DELETE route calls the concrete class and then invalidates
3288
+ * the engine cache).
3289
+ *
3290
+ * @module @omnicross/daemon/ports/JsonPricingStore
3177
3291
  */
3178
- interface ProviderKeyQuotaReader {
3179
- quotaFor(row: DaemonProviderConfig, keyId: string, options?: {
3180
- force?: boolean;
3181
- }): Promise<ProviderKeyQuota | null>;
3182
- }
3183
- interface AdminImagesStatusReader {
3184
- inspectCapability(apiKeyId: string): Promise<ImageRuntimeCapabilityInspection>;
3185
- status(): ImageRuntimeManagerStatus;
3186
- resourceStatus(): ImageRuntimeResourceStatus | undefined;
3187
- }
3188
- /** The live daemon handles the management API operates over. */
3189
- interface AdminApiDeps {
3190
- /** Path to the daemon's `config.json` (provider catalog + `server` field). */
3191
- readonly configPath: string;
3192
- /** Live provider catalog (hot-reload target). */
3193
- readonly llmConfig: ConfigFileProviderConfigSource;
3194
- /** Named outbound-key store. */
3195
- readonly keyDb: OutboundKeyDb$1;
3292
+
3293
+ declare class JsonPricingStore implements PricingStore {
3294
+ private readonly pricingPath;
3295
+ constructor(pricingPath: string);
3196
3296
  /**
3197
- * OPTIONAL voucher (redemption-card) store (voucher-redemption #9). When wired,
3198
- * the `/admin/api/voucher` surface can generate/list/revoke cards. Absent ⇒ the
3199
- * surface returns 501 (feature not available in this build).
3297
+ * Return whether the durable snapshot can actually serve at least one price.
3298
+ *
3299
+ * This intentionally checks the file itself instead of relying on refresh
3300
+ * metadata: a recent `lastSuccessAt` must not hide a deleted, truncated, or
3301
+ * otherwise unusable pricing table after a crash or manual file edit.
3200
3302
  */
3201
- readonly voucherDb?: VoucherDb;
3303
+ hasUsableSnapshot(): boolean;
3304
+ getAll(): Promise<PricingEntry[]>;
3202
3305
  /**
3203
- * OPTIONAL per-key spend reader (outbound-key-policy). When wired, the key list
3204
- * surfaces each key's OWN accumulated spend (daily/weekly/total) so the admin
3205
- * can see spend-vs-limit. Leak-safe: only the key's own numbers are exposed.
3306
+ * Insert or update one row keyed (providerId, modelId). `asUserEdit` stamps
3307
+ * user provenance (source 'user', userEdited, editedAt now) so the row is
3308
+ * protected from auto-overwrite during source refreshes; a non-user upsert
3309
+ * stamps source 'litellm' and clears nothing it should not (a plain source
3310
+ * upsert through this method overwrites the row wholesale).
3206
3311
  */
3207
- readonly keySpendReader?: KeySpendReader;
3208
- /** Outbound server settings store (server config persistence). */
3209
- readonly settingsStore: JsonApiServerSettingsStore;
3210
- /** The running outbound server (status + live applyConfig). */
3211
- readonly outboundApiServer: OutboundApiServer;
3212
- /** True only when production composed the hardened per-hop remote resolver. */
3213
- readonly imageRemoteResolverAvailable?: boolean;
3214
- /** Optional production Images runtime generation participant. */
3215
- readonly imageRuntimeConfig?: {
3216
- prepareConfig(config: ImagesServerConfig): Promise<PreparedServerConfigChange>;
3217
- };
3218
- /** Narrow metadata-only reader for authenticated Images capability/status. */
3219
- readonly imageRuntimeStatus?: AdminImagesStatusReader;
3220
- /** Metadata-only successful Images configuration audit sink. */
3221
- readonly imageConfigAudit?: (record: ImageConfigurationAuditRecord) => void;
3222
- /** Process-local machine-managed routing leases (optional for light embedders). */
3223
- readonly routeLeaseManager?: RouteLeaseManager;
3224
- /** Subscription accounts (token-free `listAll`). */
3225
- readonly subscriptionAccounts: AdminAccountsLister;
3312
+ upsert(input: PricingEntryInput, asUserEdit: boolean): Promise<PricingEntry>;
3226
3313
  /**
3227
- * Secret-free upstream allowance facade. Optional for lightweight embedders;
3228
- * the standalone daemon wires it and the route returns 501 when absent.
3314
+ * Apply a batch fetched from a pricing source. Rows whose local copy is
3315
+ * user-edited are NOT applied they come back as `{ current, incoming }`
3316
+ * conflicts; everything else is upserted with the supplied automatic source.
3317
+ * ONE file write for the whole batch.
3229
3318
  */
3230
- readonly accountAllowanceService?: AccountAllowanceAdminReader;
3231
- /** Live Claude cache worker; hot-reconfigured with allowance scheduling. */
3232
- readonly allowanceRefreshScheduler?: Pick<ClaudeAllowanceRefreshScheduler, 'configure'>;
3233
- /** Optional secret-free account connection probe + rolling history surface. */
3234
- readonly accountProbeService?: AccountProbeHistoryReader & {
3235
- probeAccount(providerId: _omnicross_contracts_subscription_types.SubscriptionProviderId, accountId: string): Promise<{
3236
- ok: boolean;
3237
- marked: boolean;
3238
- }>;
3239
- testAccountConnection(providerId: _omnicross_contracts_subscription_types.SubscriptionProviderId, accountId: string): Promise<{
3240
- ok: boolean;
3241
- marked: boolean;
3242
- tier: 'local' | 'upstream' | 'generation';
3243
- model?: string;
3319
+ bulkApplyFromSource(entries: PricingEntryInput[], source?: AutomaticPricingSource): Promise<{
3320
+ applied: PricingEntry[];
3321
+ conflicts: Array<{
3322
+ current: PricingEntry;
3323
+ incoming: PricingEntryInput;
3244
3324
  }>;
3245
- };
3246
- /**
3247
- * Least-authority subscription-token WRITER (design D4) — ONLY the mutation
3248
- * methods (`writeProviderTokens` / `clearProvider`), never a token-returning
3249
- * read. The token-free `subscriptionAccounts` lister stays separate so a GET
3250
- * handler can never reach a token through this dep.
3251
- */
3252
- readonly subscriptionTokenWriter: SubscriptionTokenWriter;
3253
- /**
3254
- * Read-only pool-health reader (key-pool design D7) — cooldown health only,
3255
- * never a key value. Drives `GET /admin/api/providers/:id/keys`.
3256
- */
3257
- readonly apiKeyPool: PoolHealthReader;
3258
- /** In-memory auto-disable store (design D5) — read-only for the health view. */
3259
- readonly autoDisableStore: AutoDisableStore;
3260
- /**
3261
- * OPTIONAL BYO provider-key quota service — same-key usage/quota probes for
3262
- * provider rows with a known adapter (Z.AI coding plan, MiniMax Token Plan).
3263
- * Absent ⇒ the keys view carries no `quota` field (light embedders).
3264
- */
3265
- readonly providerKeyQuota?: ProviderKeyQuotaReader;
3266
- /**
3267
- * Pending interactive-OAuth sessions (app-parity child 4, design D1) — the
3268
- * in-memory `{ codeVerifier, state }` map keyed by a minted `sessionId`,
3269
- * NEVER serialized to the client.
3270
- */
3271
- readonly oauthSessions: OAuthSessionStore;
3272
- /**
3273
- * Injected token-exchange `FetchLike` FACTORY (oauth design D2-a) — built per
3274
- * provider in `bootstrap.ts` so the exchange carries a `{ providerId }` egress
3275
- * ctx (per-provider proxy layer + upstream trace, bodies redacted); tests
3276
- * inject a mock so no real token endpoint is hit. Mirrors how `login.ts`
3277
- * injects its exchange fetch.
3278
- */
3279
- readonly oauthExchangeFetch: (providerId: SubscriptionProviderId) => FetchLike;
3280
- /**
3281
- * NARROW append handle (oauth design D2-a) — the OAuth complete handler needs
3282
- * `appendProviderAccount` (NOT on the least-authority `SubscriptionTokenWriter`).
3283
- * A minimal interface, NOT the full read-capable store, so no token-returning
3284
- * read is reachable. Wired from the concrete `credentialStore` in `bootstrap.ts`.
3285
- */
3286
- readonly subscriptionAccountAppender: SubscriptionAccountAppender;
3287
- /**
3288
- * Codex interactive-OAuth flow store (app-parity-2 child 5). Tracks the async
3289
- * loopback sign-in's polled status (token-free); only ONE codex login may be in
3290
- * flight (port 1455 is one resource). Wired in `bootstrap.ts`.
3291
- */
3292
- readonly codexSessions: CodexOAuthSessionStore;
3293
- /**
3294
- * Kimi interactive-OAuth flow store (device code). Same token-free polled
3295
- * shape as codex; one sign-in at a time. Wired in `bootstrap.ts`.
3296
- */
3297
- readonly kimiSessions: CodexOAuthSessionStore;
3298
- /**
3299
- * Grok interactive-OAuth flow store (device code). Same token-free polled
3300
- * shape as codex; one sign-in at a time. Wired in `bootstrap.ts`.
3301
- */
3302
- readonly grokSessions: CodexOAuthSessionStore;
3303
- /**
3304
- * Copilot interactive-OAuth flow store (device code). Same token-free
3305
- * polled shape; one sign-in at a time. Wired in `bootstrap.ts`.
3306
- */
3307
- readonly copilotSessions: CodexOAuthSessionStore;
3308
- /** Antigravity interactive OAuth (loopback 51121) — the async flow store. */
3309
- readonly antigravitySessions: CodexOAuthSessionStore;
3310
- /** The one-shot 127.0.0.1:51121 listener (test seam like `codexAwaitLoopback`). */
3311
- readonly antigravityAwaitLoopback: AntigravityLoopbackFn;
3312
- /**
3313
- * Resolve the ACTIVE antigravity account's access token for the dynamic
3314
- * model-catalog probe (secret used daemon-side only; the route is
3315
- * secret-free). Optional for older tests; absent → static-only catalog.
3316
- */
3317
- readonly resolveAntigravityAccessToken?: () => Promise<string | null>;
3318
- /**
3319
- * Codex loopback listener (app-parity-2 child 5) — defaults to `awaitLoopbackCode`
3320
- * (binds 127.0.0.1:1455) in `bootstrap.ts`; tests inject a mock so no real port
3321
- * is bound. The captured code crosses to the daemon ONLY (never the client).
3322
- */
3323
- readonly codexAwaitLoopback: CodexLoopbackFn;
3324
- /**
3325
- * Migration credential-store handle (app-parity child 6, design D2/D3). The
3326
- * export gather needs `getFullConfig()` (full DECRYPTED tokens, in-memory only —
3327
- * the pack is the only thing that leaves) and import needs `appendProviderAccount`
3328
- * (multi-account append + re-encrypt at-rest). Confined to the migration
3329
- * handlers (which seal/validate everything); never reached by a GET handler.
3330
- * Wired from the concrete `credentialStore` in `bootstrap.ts`.
3331
- */
3332
- readonly migrationCredentialStore: MigrationCredentialStore;
3333
- /**
3334
- * Usage-stats query facade (usage-pricing child) — delegates to the JSONL
3335
- * usage-event store. Aggregates only; carries no key material.
3336
- */
3337
- readonly usageRecorder: UsageRecorder;
3338
- /** Pricing engine (upsert / source refresh / conflict resolution). */
3339
- readonly pricingEngine: PricingEngine;
3340
- /**
3341
- * CONCRETE pricing store — ONLY for the store-local row `delete` (the core
3342
- * `PricingStore` port is frozen; delete is a daemon-local extra).
3343
- */
3344
- readonly pricingStore: JsonPricingStore;
3345
- /**
3346
- * External-terminal opener for the Code CLI launch route (dashboard parity).
3347
- * Optional — defaults to the real `defaultTerminalOpener`; tests inject a spy so
3348
- * no terminal window is actually spawned.
3349
- */
3350
- readonly cliTerminalOpener?: TerminalOpener;
3351
- /** Injectable PATH probe for CLI detection (tests fake "installed"). */
3352
- readonly cliPathProbe?: PathProbe;
3325
+ }>;
3353
3326
  /**
3354
- * Injectable shell runner for the Code CLI install route. Optional — defaults
3355
- * to the real `exec`-based runner; tests inject a stub so no package manager
3356
- * actually runs.
3327
+ * Apply per-row conflict decisions: 'overwrite' replaces the local row with
3328
+ * the incoming values (clearing the user-edited mark), 'skip' counts only.
3357
3329
  */
3358
- readonly cliCommandRunner?: CommandRunner;
3359
- /** Factory so each request observes the outbound server's current loopback port. */
3360
- readonly integrationManagerFactory?: () => IntegrationManager;
3330
+ applyResolutions(resolutions: Array<{
3331
+ incoming: PricingEntryInput;
3332
+ action: 'overwrite' | 'skip';
3333
+ }>): Promise<PricingResolution>;
3361
3334
  /**
3362
- * search-settings-ui D3: the daemon's ONE search runtime plus its
3363
- * bootstrap-captured frontend modes. Optional for lightweight embedders; the
3364
- * standalone daemon wires it and the `/admin/api/search` diagnostics/test
3365
- * routes return 501 when absent (the voucher/allowance optionality precedent).
3335
+ * STORE-LOCAL (not on the core port): remove one row. Returns whether a row
3336
+ * was actually removed. The admin DELETE handler calls this then invalidates
3337
+ * the engine cache.
3366
3338
  */
3367
- readonly searchStatus?: SearchAdminRuntimeStatus;
3339
+ delete(providerId: string, modelId: string): Promise<boolean>;
3340
+ /** Upsert into `rows` IN PLACE (no write) and return the resulting entry. */
3341
+ private applyUpsert;
3342
+ /** Read the pricing rows, tolerating a missing/corrupt file (→ empty list). */
3343
+ private readRows;
3344
+ private writeRows;
3345
+ /** Isolated for deterministic failure testing; never removes the target. */
3346
+ private replaceFile;
3368
3347
  }
3369
- /**
3370
- * Dispatch one `/admin/api/*` request. `path` is the already-extracted pathname
3371
- * (no query). The auth gate has already run in `AdminServer`.
3372
- */
3373
- declare function handleAdminApi(req: http.IncomingMessage, res: http.ServerResponse, path: string, deps: AdminApiDeps): Promise<void>;
3374
3348
 
3375
- /**
3376
- * AdminServer — the daemon's localhost admin/dashboard HTTP listener (RT3,
3377
- * design D1/D2).
3378
- *
3379
- * A SEPARATE `node:http` listener distinct from core's outbound `/v1/*` server
3380
- * (port 8765, untouched). Mirrors `OutboundApiServer`'s proven shape:
3381
- * - `http.createServer`, default bind `127.0.0.1` (or `0.0.0.0` when
3382
- * `networkBinding`), `listen` with `EADDRINUSE`→ephemeral(port 0) fallback,
3383
- * - `getStatus()` (running / bound port / dashboard URL), `start` / `stop`.
3384
- * Default admin port 8766.
3385
- *
3386
- * Auth (design D2):
3387
- * - Baseline = localhost bind, no token → reachable only from the machine.
3388
- * - Optional `admin.token` → every `/admin/*` request (incl. `GET /`) must
3389
- * carry `Authorization: Bearer <token>` or `X-Admin-Token: <token>`; compared
3390
- * server-side with a constant-time equality → `401` on a miss.
3391
- * - HARD SAFETY GATE: `networkBinding` (LAN/`0.0.0.0`) without a non-empty
3392
- * `admin.token` → `start` REFUSES to bind (logs + stays down, fail closed).
3393
- *
3394
- * Routing: `GET /` (and `GET /admin`) → `302 /ui/`; `* /admin/api/*` → the
3395
- * management API (`handleAdminApi`); `GET /ui[/...]` → the Control Panel static
3396
- * UI (`handleUiStatic`, from `@omnicross/ui`); everything else → `404`.
3397
- *
3398
- * @module @omnicross/daemon/admin/AdminServer
3399
- */
3349
+ interface CodexAuthHelperConfig {
3350
+ command: string;
3351
+ args: string[];
3352
+ }
3400
3353
 
3401
- /** The dependencies the admin server + its API need (live daemon handles). */
3402
- interface AdminServerDeps extends AdminApiDeps {
3403
- /** Read the resolved admin config (enabled/port/networkBinding/token). */
3404
- getAdminConfig: () => ResolvedAdminConfig;
3405
- /**
3406
- * Build the coarse, secret-free `/health` report (daemon-health-endpoint). A
3407
- * shared closure over live handles (bootstrap wires the SAME builder into the
3408
- * outbound server), served UNAUTHENTICATED — before the admin auth gate.
3409
- */
3410
- getHealthReport: () => HealthReport;
3411
- /**
3412
- * Injected logger (configurable-logging) — the admin listener's OWN lifecycle
3413
- * lines (bind/refuse/error) route through it so they honor the configured
3414
- * level / format / file sink.
3415
- */
3416
- logger: Logger;
3417
- /**
3418
- * OPTIONAL per-account probe-history reader (subscription-account-probe #8,
3419
- * design D5). When wired (bootstrap → the `AccountHealthProbeScheduler`), the
3420
- * AUTHED `GET /admin/api/account-probes` returns per-account probe history.
3421
- * Absent ⇒ the route serves an empty list (byte-safe for embedders/tests that
3422
- * do not wire it). Read-only + secret-free (ids + status labels, no tokens).
3423
- */
3424
- probeHistoryReader?: AccountProbeHistoryReader;
3425
- /**
3426
- * OPTIONAL audit query reader (request-audit-log, design D6). When wired
3427
- * (bootstrap → the date-rotated store), the AUTHED `GET /admin/api/audit`
3428
- * returns filtered records. Absent ⇒ the route serves an empty list. The
3429
- * records carry IP/UA/bodies → this route is behind the auth gate ONLY, NEVER
3430
- * unauthenticated, NEVER on `/health`.
3431
- */
3432
- auditReader?: AuditQueryReader;
3433
- /** Metadata-only audit aggregate used by the overview error-rate metric. */
3434
- auditStatsReader?: AuditStatsReader;
3435
- /**
3436
- * Reconstructs one record's bodies from the per-session shard store
3437
- * (audit-store-sharding). Absent when audit was never enabled.
3438
- */
3439
- auditBodyReader?: AuditBodyReader;
3440
- /** Runs cross-session body compaction on demand (audit-store-sharding D8). */
3441
- auditCompactor?: AuditCompactor;
3442
- /**
3443
- * OPTIONAL billing delivery-status reader (billing-event-stream, design D5).
3444
- * When wired (bootstrap → the ledger dir), the AUTHED `GET /admin/api/billing-status`
3445
- * returns secret-free total/delivered/pending counts. Absent ⇒ zeroed counts.
3446
- */
3447
- billingStatusReader?: BillingStatusReader;
3354
+ type IntegrationClientId = 'codex' | 'claude';
3355
+ type IntegrationKeyOwnership = 'managed' | 'selected';
3356
+ /** Secret-free pointer to an access key. The plaintext remains in the encrypted key store. */
3357
+ interface IntegrationKeyBinding {
3358
+ keyId: string;
3359
+ ownership: IntegrationKeyOwnership;
3448
3360
  }
3449
- /** A live status snapshot for the admin listener. */
3450
- interface AdminServerStatus {
3451
- running: boolean;
3452
- /** Actual bound port (0 when not running). */
3453
- port: number;
3454
- /** The dashboard URL (loopback or LAN base), or null when not running. */
3455
- url: string | null;
3361
+ /** Redacted access-key state exposed by the integrations admin API. */
3362
+ interface IntegrationKeyBindingStatus {
3363
+ id: string;
3364
+ name: string;
3365
+ keyPrefix: string;
3366
+ ownership: IntegrationKeyOwnership;
3367
+ revealable: boolean;
3368
+ enabled: boolean;
3369
+ revoked: boolean;
3370
+ allowedEndpoints: OutboundPermission[];
3371
+ requiredEndpoints: OutboundPermission[];
3372
+ loopbackOnly: boolean;
3456
3373
  }
3457
- declare class AdminServer {
3458
- private readonly deps;
3459
- private server;
3460
- private boundPort;
3461
- private boundAddr;
3462
- /** Control Panel dist dir (resolved once at first request; null = no UI). */
3463
- private uiDist;
3464
- constructor(deps: AdminServerDeps);
3465
- /**
3466
- * Start the admin listener honoring the resolved admin config. Returns the
3467
- * actual bound port, or `0` when it refuses/declines to bind (disabled or the
3468
- * LAN fail-closed gate). Idempotent: a second call returns the bound port.
3469
- */
3470
- start(): Promise<number>;
3471
- /** Bind once; on EADDRINUSE retry with an ephemeral port (port 0). */
3472
- private listen;
3473
- /** Per-request handler: auth gate (when a token is set) → routing. */
3474
- private onRequest;
3475
- private dispatch;
3476
- /** Constant-time bearer/header check against the configured token. */
3477
- private isAuthorized;
3478
- /** Stop the listener and release the port. */
3479
- stop(): Promise<void>;
3480
- /** A live status snapshot. */
3481
- getStatus(): AdminServerStatus;
3374
+ type IntegrationStatusKind = 'not-installed' | 'enabled' | 'configuration-drift' | 'configuration-missing' | 'key-missing';
3375
+ interface IntegrationClientStatus {
3376
+ client: IntegrationClientId;
3377
+ status: IntegrationStatusKind;
3378
+ configPath: string;
3379
+ installedAt?: number;
3380
+ gatewayBaseUrl?: string;
3381
+ message?: string;
3382
+ /** Selected key metadata only; never contains plaintext or the encrypted envelope. */
3383
+ key?: IntegrationKeyBindingStatus;
3482
3384
  }
3483
-
3484
- type DaemonImagePathArea = 'temporary' | 'artifacts' | 'state' | 'evidence' | 'mountManifest';
3485
- interface DaemonImagePaths {
3486
- readonly applicationDataRoot: string;
3487
- readonly imagesRoot: string;
3488
- readonly temporaryRoot: string;
3489
- readonly durableRoot: string;
3490
- readonly artifactsRoot: string;
3491
- readonly stateRoot: string;
3492
- readonly evidenceRoot: string;
3493
- readonly mountManifestRoot: string;
3494
- readonly mountManifestPath: string;
3385
+ interface IntegrationChangePlan {
3386
+ client: IntegrationClientId;
3387
+ configPath: string;
3388
+ action: 'install' | 'none' | 'repair';
3389
+ canApply: boolean;
3390
+ /** Redacted logical fields only; never file contents or credential values. */
3391
+ changes: string[];
3392
+ warnings: string[];
3495
3393
  }
3496
- interface ImageRootValidationOptions {
3497
- readonly label?: string;
3498
- readonly processDirectory?: string;
3499
- readonly userHome?: string;
3500
- readonly temporaryDirectory?: string;
3394
+ interface IntegrationInstallRecord {
3395
+ client: IntegrationClientId;
3396
+ configPath: string;
3397
+ originalExisted: boolean;
3398
+ /** Encrypted by IntegrationStateStore before it reaches disk. */
3399
+ originalContent: string;
3400
+ originalHash: string;
3401
+ installedHash: string;
3402
+ installedAt: number;
3403
+ gatewayBaseUrl: string;
3404
+ /** Codex auth.json snapshot and installed hash; absent on legacy records. */
3405
+ credentialFile?: IntegrationManagedFileRecord;
3501
3406
  }
3502
- interface CreateDaemonImagePathResolverOptions extends ImageRootValidationOptions {
3503
- readonly configPath: string;
3504
- readonly storageRoot?: string;
3407
+ interface IntegrationManagedFileRecord {
3408
+ path: string;
3409
+ originalExisted: boolean;
3410
+ /** Encrypted by IntegrationStateStore before it reaches disk. */
3411
+ originalContent: string;
3412
+ originalHash: string;
3413
+ installedHash: string;
3505
3414
  }
3506
- interface VerifiedDaemonImagePath {
3507
- readonly area: DaemonImagePathArea;
3508
- readonly absolutePath: string;
3509
- readonly kind: 'opaque-file' | 'opaque-directory' | 'mount-manifest';
3415
+ interface IntegrationGatewayKeyRecord {
3416
+ id: string;
3417
+ /** Encrypted by IntegrationStateStore before it reaches disk. */
3418
+ secret: string;
3419
+ createdAt: number;
3510
3420
  }
3511
- /**
3512
- * Owns all daemon Images filesystem names. Callers receive opaque capabilities,
3513
- * never a filename-accepting delete primitive; destructive methods revalidate
3514
- * the root identity, descendant relationship, basename, and symlink state.
3515
- */
3516
- declare class DaemonImagePathResolver {
3517
- #private;
3518
- readonly paths: DaemonImagePaths;
3519
- constructor(options: CreateDaemonImagePathResolverOptions);
3520
- createOpaqueFile(area: Exclude<DaemonImagePathArea, 'temporary' | 'mountManifest'>, format?: 'bin' | 'json' | 'tmp'): VerifiedDaemonImagePath;
3521
- createOpaqueDirectory(area?: Exclude<DaemonImagePathArea, 'mountManifest'>): VerifiedDaemonImagePath;
3522
- mountManifest(): VerifiedDaemonImagePath;
3523
- /** Revalidate and return one internal root for store-local bounded I/O. */
3524
- verifiedRoot(area: DaemonImagePathArea): string;
3525
- /** Revalidate immediately before unlinking a resolver-issued file capability. */
3526
- removeFile(target: VerifiedDaemonImagePath): void;
3527
- /** Only empty opaque directories may be removed until the owned-marker layer is composed. */
3528
- removeEmptyDirectory(target: VerifiedDaemonImagePath): void;
3529
- private issue;
3530
- private verifyDestructiveTarget;
3421
+ interface IntegrationState {
3422
+ version: 1;
3423
+ /** Legacy shared-key layout. New installs use `keyBindings`; retained for safe migration. */
3424
+ gatewayKey?: IntegrationGatewayKeyRecord;
3425
+ keyBindings?: Partial<Record<IntegrationClientId, IntegrationKeyBinding>>;
3426
+ clients: Partial<Record<IntegrationClientId, IntegrationInstallRecord>>;
3531
3427
  }
3532
3428
 
3533
- interface FileCodexImageCapabilityEvidenceManifestOwnerOptions {
3534
- readonly paths: DaemonImagePathResolver;
3535
- readonly maxEntries?: number;
3536
- readonly now?: () => number;
3537
- readonly random?: (bytes: number) => Buffer;
3538
- readonly hmacSalt?: Uint8Array;
3539
- readonly replaceManifest?: (targetPath: string, contents: Uint8Array) => void;
3429
+ /** Encrypted, Omnicross-owned state for reversible native CLI configuration. */
3430
+ declare class IntegrationStateStore {
3431
+ readonly path: string;
3432
+ private readonly box;
3433
+ constructor(path: string, box: SecretBox);
3434
+ load(): IntegrationState;
3435
+ save(state: IntegrationState): void;
3540
3436
  }
3541
- type FileCodexImageCapabilityEvidenceSourceOptions = Readonly<(FileCodexImageCapabilityEvidenceManifestOwnerOptions & {
3542
- readonly ttlMs: number;
3543
- }) | {
3544
- readonly owner: FileCodexImageCapabilityEvidenceManifestOwner;
3545
- readonly ttlMs: number;
3546
- }>;
3547
- interface FileCodexImageCapabilityEvidenceStatus {
3548
- readonly entries: number;
3549
- readonly freshEntries: number;
3550
- readonly staleEntries: number;
3551
- readonly bytes: number;
3437
+
3438
+ interface IntegrationManagerOptions {
3439
+ configPath: string;
3440
+ gatewayBaseUrl: string;
3441
+ keyDb: OutboundKeyDb;
3442
+ stateStore: IntegrationStateStore;
3443
+ codexAuthHelper?: CodexAuthHelperConfig;
3444
+ homeDir?: string;
3552
3445
  }
3553
- /** Revision-aware manifest owner shared by runtime generations, doctor, and cleanup. */
3554
- declare class FileCodexImageCapabilityEvidenceManifestOwner {
3555
- #private;
3556
- constructor(options: FileCodexImageCapabilityEvidenceManifestOwnerOptions);
3557
- createSource(ttlMs: number): FileCodexImageCapabilityEvidenceSource;
3558
- resolveWithTtl(request: CodexImageCapabilityEvidenceRequest, ttlMs: number): Promise<CodexImageCapabilityEvidence>;
3559
- recordSuccessfulVerificationWithTtl(observation: CodexImageCapabilityObservation, ttlMs: number): Promise<void>;
3560
- cleanup(now: number, limit: number): Promise<{
3561
- readonly entriesRemoved: number;
3562
- readonly bytesRemoved: number;
3563
- }>;
3564
- statusWithTtl(ttlMs: number): FileCodexImageCapabilityEvidenceStatus;
3565
- }
3566
- /** Immutable TTL view over a revision-aware file-backed evidence manifest owner. */
3567
- declare class FileCodexImageCapabilityEvidenceSource implements CodexImageCapabilityEvidenceSource {
3568
- #private;
3569
- constructor(options: FileCodexImageCapabilityEvidenceSourceOptions);
3570
- createView(ttlMs: number): FileCodexImageCapabilityEvidenceSource;
3571
- resolve(request: CodexImageCapabilityEvidenceRequest): Promise<CodexImageCapabilityEvidence>;
3572
- recordSuccessfulVerification(observation: CodexImageCapabilityObservation): Promise<void>;
3573
- cleanup(now: number, limit: number): Promise<{
3574
- readonly entriesRemoved: number;
3575
- readonly bytesRemoved: number;
3446
+ /** Coordinates per-client least-privilege keys with reversible native CLI config edits. */
3447
+ declare class IntegrationManager {
3448
+ private readonly options;
3449
+ private readonly homeDir;
3450
+ private readonly codexAuthHelper;
3451
+ constructor(options: IntegrationManagerOptions);
3452
+ listStatus(): Promise<IntegrationClientStatus[]>;
3453
+ plan(client: IntegrationClientId, configPath?: string): Promise<IntegrationChangePlan>;
3454
+ install(client: IntegrationClientId, configPath?: string): Promise<IntegrationClientStatus>;
3455
+ repair(client: IntegrationClientId): Promise<IntegrationClientStatus>;
3456
+ remove(client: IntegrationClientId): Promise<IntegrationClientStatus>;
3457
+ /** Bind a user-confirmed access key and grant only this client's required endpoints. */
3458
+ bindIntegrationKey(client: IntegrationClientId, keyId: string): Promise<IntegrationClientStatus>;
3459
+ /** Rotate every Omnicross-managed client binding; user-selected keys remain untouched. */
3460
+ rotateGatewayKey(): Promise<{
3461
+ keyIds: Partial<Record<IntegrationClientId, string>>;
3576
3462
  }>;
3577
- status(): FileCodexImageCapabilityEvidenceStatus;
3578
- ttlMs(): number;
3579
- /** Lifecycle-symmetric no-op; physical safety no longer depends on local leases. */
3580
- dispose(): void;
3463
+ /** Resolve the plaintext only for the command-auth helper; callers must not log it. */
3464
+ getIntegrationToken(client: IntegrationClientId): Promise<string>;
3465
+ /**
3466
+ * Resolve ONE access key's plaintext by id — the `--key-id` variant the
3467
+ * command-auth helper serves for key-scoped Codex launches (each terminal
3468
+ * picks its own gateway key, so concurrent sessions can route to different
3469
+ * upstreams through their keys' bindings). Enforces the SAME usability
3470
+ * contract as the client-bound path: existing, enabled, not revoked,
3471
+ * revealable, and holding the codex-required endpoint permissions.
3472
+ */
3473
+ getKeyToken(keyId: string): Promise<string>;
3474
+ /** Compatibility alias for callers predating per-client bindings. */
3475
+ getGatewayToken(client?: IntegrationClientId): Promise<string>;
3476
+ private ensureClientKey;
3477
+ private createManagedClientKey;
3478
+ private installedSecret;
3479
+ private rebindInstalledClient;
3480
+ private statusFor;
3481
+ private boundKeyDetails;
3482
+ private retireManagedKeys;
3483
+ private defaultConfigPath;
3484
+ private renderInstalled;
3581
3485
  }
3582
3486
 
3583
- interface FileImageReferenceStoreLimits {
3584
- readonly ttlMs: number;
3585
- readonly maxArtifactBytes: number;
3586
- readonly maxTotalBytes: number;
3587
- readonly maxTenantBytes: number;
3588
- readonly maxEntries: number;
3589
- readonly maxTombstones: number;
3590
- readonly tombstoneTtlMs: number;
3591
- }
3592
- interface FileImageReferenceStoreOptions {
3593
- readonly paths: DaemonImagePathResolver;
3594
- readonly limits: FileImageReferenceStoreLimits;
3595
- readonly secretBox?: SecretBox;
3596
- readonly now?: () => number;
3597
- readonly random?: (bytes: number) => Buffer;
3598
- readonly replaceManifest?: (targetPath: string, contents: Uint8Array) => void;
3599
- }
3600
- interface FileImageReferenceReconciliationResult {
3601
- readonly metadataRemoved: number;
3602
- readonly metadataDegradedToProviderReference: number;
3603
- readonly orphanFilesRemoved: number;
3604
- readonly incompleteFilesRemoved: number;
3605
- readonly invalidDescendants: number;
3606
- }
3607
- declare class FileImageReferenceStore implements ImageReferenceStore {
3608
- #private;
3609
- constructor(options: FileImageReferenceStoreOptions);
3610
- save(input: ImageReferenceSaveInput): Promise<ImageReferenceMetadata>;
3611
- /** Generation-bound write entry point; reads and maintenance remain shared. */
3612
- saveWithLimits(input: ImageReferenceSaveInput, limits: FileImageReferenceStoreLimits): Promise<ImageReferenceMetadata>;
3613
- /** Updates only app-session maintenance policy; pinned writes pass their own limits. */
3614
- updateMaintenanceLimits(limits: FileImageReferenceStoreLimits): void;
3615
- resolve(tenantId: string, referenceId: ImageReferenceId): Promise<ImageReferenceResolution>;
3616
- delete(tenantId: string, referenceId: ImageReferenceId): Promise<boolean>;
3617
- /** Daemon-internal cleanup path; accepts only the local reference-domain tenant HMAC. */
3618
- deleteByHashedTenantKey(tenantKey: string, referenceId: ImageReferenceId): Promise<boolean>;
3619
- cleanup(now?: number): Promise<number>;
3620
- status(): {
3621
- readonly entries: number;
3622
- readonly bytes: number;
3623
- readonly tombstones: number;
3624
- };
3625
- hasLiveReferenceByHashedTenantKey(tenantKey: string, referenceId: ImageReferenceId, now?: number): Promise<boolean>;
3626
- reconcileOwnedFiles(maxEntries: number): Promise<FileImageReferenceReconciliationResult>;
3627
- openArtifact(fileName: string, byteLength: number, signal?: AbortSignal): Promise<ReadableStream<Uint8Array>>;
3628
- private exclusive;
3629
- private tenantKey;
3630
- private newReferenceId;
3631
- private validateSaveInput;
3632
- private selectVictims;
3633
- private nextTombstones;
3634
- private writeArtifact;
3635
- private artifactPath;
3636
- private validArtifact;
3637
- private removeArtifact;
3638
- private safeUnlinkArtifactPath;
3639
- private releaseLease;
3640
- private manifestPath;
3641
- private persist;
3642
- private atomicReplace;
3643
- private loadManifest;
3644
- }
3487
+ /**
3488
+ * accountsAntigravityOAuth — the daemon admin API's ANTIGRAVITY interactive
3489
+ * OAuth path (`POST /accounts/antigravity/oauth/start` +
3490
+ * `GET /accounts/antigravity/oauth/:sessionId/status`).
3491
+ *
3492
+ * Modeled on the codex loopback flow (`accountsCodexOAuth`): antigravity's
3493
+ * redirect is a FIXED loopback `http://127.0.0.1:51121/oauth-callback`, so the
3494
+ * flow is ASYNC + POLLED — `start` arms the one-shot loopback listener, kicks
3495
+ * the capture→exchange→email→project-handshake→persist chain off ASYNC, and
3496
+ * returns ONLY `{ authUrl, sessionId }` (public — client_id + state). The app
3497
+ * opens `authUrl`; the browser redirects to the loopback; the daemon captures
3498
+ * the `code`, validates `state`, exchanges it, resolves the userinfo email and
3499
+ * the Code Assist project (the antigravity dialect of the shared resolver),
3500
+ * and persists the minted token through the encrypted credential store. The
3501
+ * app POLLS `status` until `done`/`error`.
3502
+ *
3503
+ * SECRET SPINE (same invariant as codex/grok): the minted access/refresh token
3504
+ * NEVER crosses to the client — it lands ONLY in the encrypted store. The
3505
+ * poll `status` body is TOKEN-FREE (`{ state, message? }`). Port 51121 is a
3506
+ * single resource → only ONE antigravity sign-in may be in flight (409).
3507
+ *
3508
+ * REUSES the `@omnicross/subscriptions` antigravity flow + the CLI's
3509
+ * `awaitLoopbackCode` listener (parameterized binding) — it does NOT rebuild
3510
+ * the authorize/exchange/handshake logic.
3511
+ *
3512
+ * @module @omnicross/daemon/admin/accountsAntigravityOAuth
3513
+ */
3645
3514
 
3646
- interface FileResponsesImageStateStoreLimits {
3647
- readonly maxCalls: number;
3648
- readonly maxResponses: number;
3649
- readonly maxTombstones: number;
3650
- readonly tombstoneTtlMs: number;
3651
- }
3652
- interface FileResponsesImageStateStoreOptions {
3653
- readonly paths: DaemonImagePathResolver;
3654
- readonly limits: FileResponsesImageStateStoreLimits;
3655
- readonly now?: () => number;
3656
- readonly random?: (bytes: number) => Buffer;
3657
- readonly replaceManifest?: (targetPath: string, contents: Uint8Array) => void;
3658
- }
3659
- interface PendingResponsesImageReferenceDelete {
3660
- readonly referenceTenantKey: string;
3661
- readonly binding: ResponsesImageCallBinding;
3662
- }
3663
- /** Durable production implementation of the existing Responses image-state contract. */
3664
- declare class FileResponsesImageStateStore implements ResponsesImageStateStore {
3665
- #private;
3666
- constructor(options: FileResponsesImageStateStoreOptions);
3667
- commit(input: ResponsesImageStateCommitInput): Promise<readonly ResponsesImageCallBinding[]>;
3668
- /** Generation-bound write entry point; reads and maintenance remain shared. */
3669
- commitWithLimits(input: ResponsesImageStateCommitInput, limits: FileResponsesImageStateStoreLimits): Promise<readonly ResponsesImageCallBinding[]>;
3670
- /** Updates only app-session maintenance policy; pinned commits pass their own limits. */
3671
- updateMaintenanceLimits(limits: FileResponsesImageStateStoreLimits): void;
3672
- resolveCall(tenantId: string, callId: ResponsesImageCallId): Promise<ResponsesImageCallResolution>;
3673
- resolveResponse(tenantId: string, responseId: string): Promise<ResponsesImageResponseResolution>;
3674
- deleteCall(tenantId: string, callId: ResponsesImageCallId): Promise<ResponsesImageCallBinding | undefined>;
3675
- deleteResponse(tenantId: string, responseId: string): Promise<boolean>;
3676
- cleanup(now?: number): Promise<readonly ResponsesImageCallBinding[]>;
3677
- pendingReferenceDeletes(limit?: number): readonly PendingResponsesImageReferenceDelete[];
3678
- acknowledgeReferenceDeletes(completed: readonly PendingResponsesImageReferenceDelete[]): Promise<number>;
3679
- reconcileBrokenReferenceLinks(hasLiveReference: (referenceTenantKey: string, referenceId: ResponsesImageCallBinding['referenceId']) => Promise<boolean>, maxEntries: number): Promise<readonly ResponsesImageCallBinding[]>;
3680
- status(): {
3681
- readonly calls: number;
3682
- readonly responses: number;
3683
- readonly tombstones: number;
3684
- readonly pendingReferenceDeletes: number;
3685
- };
3686
- private exclusive;
3687
- private failure;
3688
- private assertCommit;
3689
- private tenantKey;
3690
- private rememberTombstone;
3691
- private enqueuePendingReferenceDelete;
3692
- private hasTombstone;
3693
- private prunedTombstones;
3694
- private pruneTombstonesInPlace;
3695
- private sameTombstones;
3696
- private touch;
3697
- private releaseCall;
3698
- private releaseResponse;
3699
- private manifestPath;
3700
- private persist;
3701
- private atomicReplace;
3702
- private loadManifest;
3703
- }
3515
+ /** The loopback-listener fn (injected so tests need not bind a real port). */
3516
+ type AntigravityLoopbackFn = (state: string, timeoutMs?: number, signal?: AbortSignal) => Promise<string>;
3704
3517
 
3705
- interface ImageStorageMountBackend {
3706
- readonly id: string;
3707
- readonly createdAt: number;
3708
- readonly resolver: DaemonImagePathResolver;
3709
- readonly references: FileImageReferenceStore;
3710
- readonly responsesState: FileResponsesImageStateStore;
3711
- }
3712
- interface ImageStorageMountCatalogOptions {
3713
- readonly pathOptions: Omit<CreateDaemonImagePathResolverOptions, 'storageRoot'>;
3714
- readonly activeStorageRoot?: string;
3715
- readonly referenceLimits: FileImageReferenceStoreLimits;
3716
- readonly responsesStateLimits: FileResponsesImageStateStoreLimits;
3717
- readonly secretBox?: SecretBox;
3718
- readonly now?: () => number;
3719
- readonly random?: (bytes: number) => Buffer;
3720
- readonly replaceCatalog?: (targetPath: string, contents: Uint8Array) => void;
3721
- readonly reconcileCorruptManifests?: boolean;
3722
- }
3723
- interface ImageStorageMountPolicy {
3724
- readonly referenceLimits: FileImageReferenceStoreLimits;
3725
- readonly responsesStateLimits: FileResponsesImageStateStoreLimits;
3726
- }
3727
- interface PreparedImageStorageMountActivation {
3728
- readonly backend: ImageStorageMountBackend;
3729
- publish(): ImageStorageMountBackend;
3730
- rollback(): void;
3731
- dispose(): void;
3732
- }
3733
- /** Owns the durable-root set independently from any one runtime generation. */
3734
- declare class ImageStorageMountCatalog {
3735
- #private;
3736
- constructor(options: ImageStorageMountCatalogOptions);
3737
- active(): ImageStorageMountBackend;
3738
- mountsForRead(): readonly ImageStorageMountBackend[];
3739
- status(): {
3740
- readonly mounts: number;
3741
- readonly retiredMounts: number;
3742
- };
3743
- startupReconciliationStatus(): {
3744
- readonly corruptManifestsQuarantined: number;
3745
- };
3746
- utilization(): {
3747
- readonly referenceEntries: number;
3748
- readonly referenceBytes: number;
3749
- readonly referenceTombstones: number;
3750
- readonly stateCalls: number;
3751
- readonly stateResponses: number;
3752
- readonly stateTombstones: number;
3753
- readonly pendingReferenceDeletes: number;
3754
- };
3755
- /** Pins a backend object until its owning runtime generation drains. */
3756
- retainBackend(backend: ImageStorageMountBackend): () => void;
3757
- activate(storageRoot?: string): ImageStorageMountBackend;
3758
- /** Prepare a validated backend without changing the catalog's active mount. */
3759
- prepareActivation(storageRoot?: string, policy?: ImageStorageMountPolicy): PreparedImageStorageMountActivation;
3760
- retireEmptyMount(mountId: string): boolean;
3761
- private createResolver;
3762
- private createBackend;
3763
- private applyMaintenancePolicy;
3764
- private newMountId;
3765
- private isManifestError;
3766
- private quarantineManifest;
3767
- private isVerifiedEmpty;
3768
- private catalogPath;
3769
- private persist;
3770
- private atomicReplace;
3771
- private loadCatalog;
3518
+ /**
3519
+ * cliLaunch — the admin API's "launch a coding CLI in a terminal, pointed at the
3520
+ * daemon" surface (dashboard parity with the desktop app's Code CLI tab).
3521
+ *
3522
+ * This is the EXTERNAL-terminal analogue of `commands/launch.ts`: it reuses the
3523
+ * same `@omnicross/cli-launcher` builders (which register one route on the
3524
+ * RESIDENT `ProviderProxy` and return the redirect env — `ANTHROPIC_BASE_URL` +
3525
+ * a one-shot ROUTE token, codex's `-c base_url=…` overrides, etc.), then opens a
3526
+ * NEW terminal window running the CLI with that env injected. The route token —
3527
+ * NOT an upstream credential — is the only secret in the env; it is removed when
3528
+ * the session is stopped (`onSessionEnd`).
3529
+ *
3530
+ * SECRET DISCIPLINE: the env carries a route token (proxy-scoped, revocable),
3531
+ * never a provider key. On win32 the token rides the spawned process environment
3532
+ * (inherited by the terminal), never the command line / a file on disk.
3533
+ *
3534
+ * KEY-SCOPED LAUNCH (`{ keyId }` body, codex only): instead of a route lease, the
3535
+ * terminal's Codex authenticates to the RESIDENT outbound gateway as ONE chosen
3536
+ * access key, so routing follows that key's gateway bindings. Concurrent
3537
+ * terminals can then use different keys (hence different upstreams) at once.
3538
+ * The redirect rides `-c` overrides reusing the INSTALLED provider name
3539
+ * (`omnicross`) plus a `--key-id`-scoped auth command; no secret ever enters the
3540
+ * spawned env (Codex invokes the helper itself).
3541
+ *
3542
+ * @module @omnicross/daemon/admin/cliLaunch
3543
+ */
3544
+
3545
+ /** Injectable PATH probe (tests stub this; default scans `process.env.PATH`). */
3546
+ type PathProbe = (candidate: string) => string | null;
3547
+ /** Open a NEW terminal window running `command [extraArgs…]` with `env` injected. */
3548
+ type TerminalCleanup = () => void;
3549
+ type TerminalOpener = (input: {
3550
+ cli: string;
3551
+ command: string;
3552
+ extraArgs: string[];
3553
+ env: Record<string, string>;
3554
+ cwd?: string;
3555
+ platform: NodeJS.Platform;
3556
+ onFailure?: () => void;
3557
+ }) => void | TerminalCleanup;
3558
+ /**
3559
+ * Injectable shell runner for `POST /cli/:cli/install` (tests stub this; the
3560
+ * default execs the install command with a bounded timeout). Returns the host's
3561
+ * honest install outcome — `error` carries stderr/the failure reason.
3562
+ */
3563
+ type CommandRunner = (command: string) => Promise<{
3564
+ ok: boolean;
3565
+ error?: string;
3566
+ }>;
3567
+
3568
+ /**
3569
+ * searchAdminApi — the admin API's search surface (search-settings-ui D3 +
3570
+ * search-settings-tab D4).
3571
+ *
3572
+ * Three routes over the daemon's search state, dispatched from `adminApi.ts`'s
3573
+ * `case 'search'`:
3574
+ *
3575
+ * - `GET /admin/api/search/diagnostics` — a READ-ONLY, secret-free, network-free
3576
+ * snapshot: one row per provider the daemon can run (the ONE runtime's
3577
+ * descriptors, plus `unconfigured` rows for known API providers the persisted
3578
+ * config does not name — the doctor's classification), the effective frontend
3579
+ * modes, and the explicit apply semantics (codex immediate, rest restart).
3580
+ * - `POST /admin/api/search/test { providerId }` — ONE live fixed-query check on
3581
+ * a configured provider, classified by the doctor's pure functions. The
3582
+ * machine-facing health probe: it sends exactly `SEARCH_DOCTOR_QUERY`, never a
3583
+ * caller-supplied query, and never returns result content (plan §11.3 — its
3584
+ * contract is the automated doctor's fixed-query discipline).
3585
+ * - `POST /admin/api/search/query { providerId, query }` — the INTERACTIVE
3586
+ * channel for the settings page's per-provider test panel (owner feedback
3587
+ * 2026-09-02): ONE operator-typed query through ONE provider's contribution
3588
+ * built from the PERSISTED config, returning the doctor-classified diagnostic
3589
+ * PLUS the sanitized results. The two disciplines stay separate routes on
3590
+ * purpose: bending `/test` to accept a query would erase the boundary its
3591
+ * pinned tests and consumers depend on.
3592
+ *
3593
+ * SECRET SPINE (all three routes): no response ever carries a configured VALUE,
3594
+ * and a failure response carries only the doctor's SANITIZED error shape — raw
3595
+ * upstream error bodies (which may quote the stored key) never serialize. The
3596
+ * query endpoint additionally sanitizes every returned result field BEFORE
3597
+ * serialization (plan §11.1: search results are untrusted input), and the
3598
+ * operator's query is never logged anywhere.
3599
+ *
3600
+ * The diagnostics dep is OPTIONAL (`AdminApiDeps.searchStatus`): light embedders
3601
+ * that wire no search runtime get 501 for all routes (the voucher/allowance
3602
+ * optionality precedent) rather than a fabricated snapshot.
3603
+ *
3604
+ * @module @omnicross/daemon/admin/searchAdminApi
3605
+ */
3606
+
3607
+ /**
3608
+ * The daemon search state the admin surface needs. Structurally satisfied by
3609
+ * what `bootstrap.ts` already holds (the ONE runtime + its captured modes);
3610
+ * `testFetch` is a TEST SEAM so route tests can intercept the one live probe
3611
+ * without any network.
3612
+ */
3613
+ interface SearchAdminRuntimeStatus {
3614
+ /** The daemon's ONE assembled search runtime (provider descriptors). */
3615
+ readonly runtime: SearchRuntime;
3616
+ /** Modes as captured at bootstrap (responses/anthropic are these, live). */
3617
+ readonly modes: SearchFrontendModes;
3618
+ /** TEST SEAM: fetch primitive for the live-test probe. Absent ⇒ real transport. */
3619
+ readonly testFetch?: (url: string, init: RequestInit) => Promise<Response>;
3772
3620
  }
3773
- declare class MountedImageReferenceStore implements ImageReferenceStore {
3774
- private readonly catalog;
3775
- private readonly writeBackend?;
3776
- private readonly writeLimits?;
3777
- constructor(catalog: ImageStorageMountCatalog, writeBackend?: ImageStorageMountBackend | undefined, writeLimits?: FileImageReferenceStoreLimits | undefined);
3778
- bindWriteBackend(backend: ImageStorageMountBackend, limits: FileImageReferenceStoreLimits): MountedImageReferenceStore;
3779
- status(): Readonly<{
3780
- referenceEntries: number;
3781
- referenceBytes: number;
3782
- referenceTombstones: number;
3783
- stateCalls: number;
3784
- stateResponses: number;
3785
- stateTombstones: number;
3786
- pendingReferenceDeletes: number;
3787
- mounts: number;
3788
- retiredMounts: number;
3789
- }>;
3790
- save(input: ImageReferenceSaveInput): Promise<_omnicross_contracts_image_generation_types.ImageReferenceMetadata>;
3791
- resolve(tenantId: string, referenceId: ImageReferenceId): Promise<ImageReferenceResolution>;
3792
- delete(tenantId: string, referenceId: ImageReferenceId): Promise<boolean>;
3793
- deleteByHashedTenantKey(tenantKey: string, referenceId: ImageReferenceId): Promise<boolean>;
3794
- cleanup(now?: number): Promise<number>;
3621
+
3622
+ /**
3623
+ * migration.ts — the export gather + import apply logic for the passphrase pack
3624
+ * (app-parity child 6, design D2/D3/D5).
3625
+ *
3626
+ * EXPORT (`gatherExport`): read the FULL local state DECRYPTED in-memory — every
3627
+ * provider row (scalars / modelConfigs / single apiKey / pool apiKeys /
3628
+ * transformer) via `loadConfig` (the at-rest box decrypts on read) AND the
3629
+ * subscription tokens via `credentialStore.getFullConfig()` (also decrypted) —
3630
+ * serialize to ONE bundle JSON, and `sealPack` it under the passphrase-derived
3631
+ * key. The caller returns ONLY the opaque pack; the decrypted bundle + the
3632
+ * passphrase live only in local variables and are never logged.
3633
+ *
3634
+ * IMPORT (`applyImport`): `openPack` decrypts + authenticates (a wrong passphrase
3635
+ * or a tampered pack fails the GCM auth-tag BEFORE any write — atomic). Every
3636
+ * provider is re-validated through `parseProviderInput` and every token block
3637
+ * through `validateTokenBody` (deny-by-default — a malicious blob cannot inject
3638
+ * unknown fields or escape the allowlist). Validation collects ALL rows BEFORE
3639
+ * applying, so a structurally invalid pack does not leave a half-applied state.
3640
+ * Apply merges by provider id (additive default): a new id is added; a colliding
3641
+ * id is skipped (or overwritten with `mode:'overwrite'`). Writes go through the
3642
+ * EXISTING paths (`saveConfig` re-encrypts at-rest under the LOCAL box +
3643
+ * `writeProviderTokens`/`appendProviderAccount`), so imported secrets land
3644
+ * `enc:`-encrypted under the TARGET machine's key — the passphrase key is used
3645
+ * ONLY for transport.
3646
+ *
3647
+ * SECRET SPINE: the export RESPONSE is the opaque pack ONLY; the import RESPONSE
3648
+ * is status-only counts. No decrypted secret + no passphrase ever reaches a
3649
+ * response body or a log here.
3650
+ *
3651
+ * @module @omnicross/daemon/migration/migration
3652
+ */
3653
+
3654
+ /**
3655
+ * The credential-store surface the migration paths need: the full DECRYPTED read
3656
+ * (export) + the multi-account append (import re-encrypts at-rest). One shape so
3657
+ * `ExportDeps` + `ImportDeps` can be unified into `MigrationDeps` without a
3658
+ * `credentialStore` type conflict.
3659
+ */
3660
+ interface MigrationCredentialStore extends SubscriptionAccountAppender {
3661
+ getFullConfig(): Promise<AccountTokensConfig>;
3795
3662
  }
3796
- declare class MountedResponsesImageStateStore implements ResponsesImageStateStore {
3797
- private readonly catalog;
3798
- private readonly writeBackend?;
3799
- private readonly writeLimits?;
3800
- constructor(catalog: ImageStorageMountCatalog, writeBackend?: ImageStorageMountBackend | undefined, writeLimits?: FileResponsesImageStateStoreLimits | undefined);
3801
- bindWriteBackend(backend: ImageStorageMountBackend, limits: FileResponsesImageStateStoreLimits): MountedResponsesImageStateStore;
3802
- commit(input: ResponsesImageStateCommitInput): Promise<readonly ResponsesImageCallBinding[]>;
3803
- resolveCall(tenantId: string, callId: ResponsesImageCallId): Promise<ResponsesImageCallResolution>;
3804
- resolveResponse(tenantId: string, responseId: string): Promise<ResponsesImageResponseResolution>;
3805
- deleteCall(tenantId: string, callId: ResponsesImageCallId): Promise<ResponsesImageCallBinding | undefined>;
3806
- deleteResponse(tenantId: string, responseId: string): Promise<boolean>;
3807
- cleanup(now?: number): Promise<readonly ResponsesImageCallBinding[]>;
3663
+
3664
+ /** Minimal, auth-gated admin API for secret-free account allowance snapshots. */
3665
+
3666
+ interface AccountAllowanceAdminReader {
3667
+ list(filter?: {
3668
+ providerId?: SubscriptionProviderId;
3669
+ accountId?: string;
3670
+ }): Promise<AccountAllowanceSnapshot[]>;
3671
+ refreshClaude(accountId?: string): Promise<AccountAllowanceSnapshot[]>;
3672
+ /** Optional: Codex active `/wham/usage` refresh (absent on older daemons). */
3673
+ refreshCodex?(accountId?: string): Promise<AccountAllowanceSnapshot[]>;
3674
+ /** Optional: Kimi `/coding/v1/usages` refresh (absent on older daemons). */
3675
+ refreshKimi?(accountId?: string): Promise<AccountAllowanceSnapshot[]>;
3676
+ /** Optional: OpenCodeGo `/v1/usage` refresh (absent on older daemons). */
3677
+ refreshOpenCodeGo?(accountId?: string): Promise<AccountAllowanceSnapshot[]>;
3678
+ /** Optional: Grok CLI-billing refresh (absent on older daemons). */
3679
+ refreshGrok?(accountId?: string): Promise<AccountAllowanceSnapshot[]>;
3680
+ /** Optional: Copilot user-quota refresh (absent on older daemons). */
3681
+ refreshCopilot?(accountId?: string): Promise<AccountAllowanceSnapshot[]>;
3682
+ /** Optional: Gemini Code-Assist quota refresh (absent on older daemons). */
3683
+ refreshGemini?(accountId?: string): Promise<AccountAllowanceSnapshot[]>;
3684
+ /** Optional: Antigravity quotaSummary refresh (absent on older daemons). */
3685
+ refreshAntigravity?(accountId?: string): Promise<AccountAllowanceSnapshot[]>;
3686
+ removeAccountSnapshot?(providerId: SubscriptionProviderId, accountId: string): void;
3687
+ removeProviderSnapshots?(providerId: SubscriptionProviderId): void;
3688
+ getSchedulingStatus?(): AccountAllowanceSchedulingStatus;
3808
3689
  }
3809
3690
 
3810
- interface ImageDoctorLocalSnapshot {
3811
- readonly config: Readonly<{
3812
- enabled: boolean;
3813
- provider: ImageProviderId;
3814
- model: string;
3815
- valid: boolean;
3816
- errorCount: number;
3817
- /** Distinct providers the routing table names (doctor per-provider rows). */
3818
- routedProviders: readonly ImageProviderId[];
3819
- }>;
3820
- readonly roots: Readonly<{
3821
- valid: boolean;
3822
- verifiedAreas: number;
3823
- expectedAreas: number;
3824
- }>;
3825
- readonly stores: Readonly<{
3826
- valid: boolean;
3827
- mounts: number;
3828
- retiredMounts: number;
3829
- referenceEntries: number;
3830
- referenceBytes: number;
3831
- stateCalls: number;
3832
- stateResponses: number;
3833
- corruptManifestsQuarantined: number;
3834
- }>;
3835
- readonly permissions: Readonly<{
3836
- valid: boolean;
3837
- rows: number;
3838
- legacyRows: number;
3839
- invalidRows: number;
3840
- imagesAuthorizedRows: number;
3841
- }>;
3842
- /** The Codex-subscription account backing codex-routed image models. */
3843
- readonly account: Readonly<{
3844
- present: boolean;
3845
- usable: boolean;
3846
- reason: 'ready' | 'missing' | 'unavailable';
3847
- }>;
3848
- /** The Antigravity-subscription account backing NanoBanana image models. */
3849
- readonly antigravityAccount: Readonly<{
3850
- present: boolean;
3851
- usable: boolean;
3852
- reason: 'ready' | 'missing' | 'unavailable';
3853
- }>;
3854
- readonly evidence: Readonly<FileCodexImageCapabilityEvidenceStatus & {
3855
- valid: boolean;
3856
- }>;
3691
+ /** Token-free subscription account list entry (passthrough from core's service). */
3692
+ interface AdminAccountsLister {
3693
+ listAll(): Promise<unknown[]>;
3857
3694
  }
3858
- type ImageDoctorLiveFailureCode = 'images_disabled' | 'codex_account_unavailable' | 'evidence_store_unavailable' | 'evidence_persist_failed' | ImageGenerationErrorCode;
3859
- type ImageDoctorLiveResult = Readonly<{
3860
- ok: true;
3861
- code: 'verified';
3862
- model: 'gpt-image-2';
3863
- quality: 'low';
3864
- outputFormat: 'png';
3865
- freshEvidenceEntries: number;
3866
- }> | Readonly<{
3867
- ok: false;
3868
- code: ImageDoctorLiveFailureCode;
3869
- }>;
3870
- interface ImageDoctorService {
3871
- inspectLocal(config: ImagesServerConfig): Promise<ImageDoctorLocalSnapshot>;
3872
- verifyLive(config: ImagesServerConfig, signal: AbortSignal): Promise<ImageDoctorLiveResult>;
3695
+ /** One key's live cooldown health (mirrors core `KeyHealthEntry`; read-only). */
3696
+ interface PoolKeyHealth {
3697
+ until: number;
3698
+ errors: number;
3699
+ lastStatus: number | null;
3700
+ }
3701
+ /**
3702
+ * The READ-ONLY pool-health surface the admin view needs (key-pool design D7).
3703
+ * Structurally satisfied by core's `ApiKeyPoolService.getKeyHealth`; typed as a
3704
+ * minimal reader so `adminApi` carries no class coupling and can never reach a
3705
+ * key value through it (cooldown health only).
3706
+ */
3707
+ interface PoolHealthReader {
3708
+ getKeyHealth(providerId: string): Promise<Record<string, PoolKeyHealth>>;
3709
+ }
3710
+ /**
3711
+ * The BYO provider-key quota surface the keys view needs — structurally
3712
+ * satisfied by `ProviderKeyQuotaService`. Read-only; the DTO is secret-free by
3713
+ * construction (normalized windows + diagnostic codes only).
3714
+ */
3715
+ interface ProviderKeyQuotaReader {
3716
+ quotaFor(row: DaemonProviderConfig, keyId: string, options?: {
3717
+ force?: boolean;
3718
+ }): Promise<ProviderKeyQuota | null>;
3719
+ }
3720
+ interface AdminImagesStatusReader {
3721
+ inspectCapability(apiKeyId: string): Promise<ImageRuntimeCapabilityInspection>;
3722
+ status(): ImageRuntimeManagerStatus;
3723
+ resourceStatus(): ImageRuntimeResourceStatus | undefined;
3724
+ }
3725
+ /** The live daemon handles the management API operates over. */
3726
+ interface AdminApiDeps {
3727
+ /** Path to the daemon's `config.json` (provider catalog + `server` field). */
3728
+ readonly configPath: string;
3729
+ /** Live provider catalog (hot-reload target). */
3730
+ readonly llmConfig: ConfigFileProviderConfigSource;
3731
+ /** Named outbound-key store. */
3732
+ readonly keyDb: OutboundKeyDb$1;
3733
+ /**
3734
+ * OPTIONAL voucher (redemption-card) store (voucher-redemption #9). When wired,
3735
+ * the `/admin/api/voucher` surface can generate/list/revoke cards. Absent ⇒ the
3736
+ * surface returns 501 (feature not available in this build).
3737
+ */
3738
+ readonly voucherDb?: VoucherDb;
3739
+ /**
3740
+ * OPTIONAL per-key spend reader (outbound-key-policy). When wired, the key list
3741
+ * surfaces each key's OWN accumulated spend (daily/weekly/total) so the admin
3742
+ * can see spend-vs-limit. Leak-safe: only the key's own numbers are exposed.
3743
+ */
3744
+ readonly keySpendReader?: KeySpendReader;
3745
+ /** Outbound server settings store (server config persistence). */
3746
+ readonly settingsStore: JsonApiServerSettingsStore;
3747
+ /** The running outbound server (status + live applyConfig). */
3748
+ readonly outboundApiServer: OutboundApiServer;
3749
+ /** True only when production composed the hardened per-hop remote resolver. */
3750
+ readonly imageRemoteResolverAvailable?: boolean;
3751
+ /** Optional production Images runtime generation participant. */
3752
+ readonly imageRuntimeConfig?: {
3753
+ prepareConfig(config: ImagesServerConfig): Promise<PreparedServerConfigChange>;
3754
+ };
3755
+ /** Narrow metadata-only reader for authenticated Images capability/status. */
3756
+ readonly imageRuntimeStatus?: AdminImagesStatusReader;
3757
+ /**
3758
+ * Explicitly-consuming live image verification (images-settings-tab D3) —
3759
+ * delegates to the doctor service's `verifyLive` (Codex wire only). Absent
3760
+ * on light embedders; the endpoint then responds 501 and consumes nothing.
3761
+ */
3762
+ readonly imageLiveVerifier?: {
3763
+ verifyLive(config: ImagesServerConfig, signal: AbortSignal): Promise<ImageDoctorLiveResult>;
3764
+ };
3765
+ /** Metadata-only successful Images configuration audit sink. */
3766
+ readonly imageConfigAudit?: (record: ImageConfigurationAuditRecord) => void;
3767
+ /** Process-local machine-managed routing leases (optional for light embedders). */
3768
+ readonly routeLeaseManager?: RouteLeaseManager;
3769
+ /** Subscription accounts (token-free `listAll`). */
3770
+ readonly subscriptionAccounts: AdminAccountsLister;
3771
+ /**
3772
+ * Secret-free upstream allowance facade. Optional for lightweight embedders;
3773
+ * the standalone daemon wires it and the route returns 501 when absent.
3774
+ */
3775
+ readonly accountAllowanceService?: AccountAllowanceAdminReader;
3776
+ /** Live Claude cache worker; hot-reconfigured with allowance scheduling. */
3777
+ readonly allowanceRefreshScheduler?: Pick<ClaudeAllowanceRefreshScheduler, 'configure'>;
3778
+ /** Optional secret-free account connection probe + rolling history surface. */
3779
+ readonly accountProbeService?: AccountProbeHistoryReader & {
3780
+ probeAccount(providerId: _omnicross_contracts_subscription_types.SubscriptionProviderId, accountId: string): Promise<{
3781
+ ok: boolean;
3782
+ marked: boolean;
3783
+ }>;
3784
+ testAccountConnection(providerId: _omnicross_contracts_subscription_types.SubscriptionProviderId, accountId: string): Promise<{
3785
+ ok: boolean;
3786
+ marked: boolean;
3787
+ tier: 'local' | 'upstream' | 'generation';
3788
+ model?: string;
3789
+ }>;
3790
+ };
3791
+ /**
3792
+ * Least-authority subscription-token WRITER (design D4) — ONLY the mutation
3793
+ * methods (`writeProviderTokens` / `clearProvider`), never a token-returning
3794
+ * read. The token-free `subscriptionAccounts` lister stays separate so a GET
3795
+ * handler can never reach a token through this dep.
3796
+ */
3797
+ readonly subscriptionTokenWriter: SubscriptionTokenWriter;
3798
+ /**
3799
+ * Read-only pool-health reader (key-pool design D7) — cooldown health only,
3800
+ * never a key value. Drives `GET /admin/api/providers/:id/keys`.
3801
+ */
3802
+ readonly apiKeyPool: PoolHealthReader;
3803
+ /** In-memory auto-disable store (design D5) — read-only for the health view. */
3804
+ readonly autoDisableStore: AutoDisableStore;
3805
+ /**
3806
+ * OPTIONAL BYO provider-key quota service — same-key usage/quota probes for
3807
+ * provider rows with a known adapter (Z.AI coding plan, MiniMax Token Plan).
3808
+ * Absent ⇒ the keys view carries no `quota` field (light embedders).
3809
+ */
3810
+ readonly providerKeyQuota?: ProviderKeyQuotaReader;
3811
+ /**
3812
+ * Pending interactive-OAuth sessions (app-parity child 4, design D1) — the
3813
+ * in-memory `{ codeVerifier, state }` map keyed by a minted `sessionId`,
3814
+ * NEVER serialized to the client.
3815
+ */
3816
+ readonly oauthSessions: OAuthSessionStore;
3817
+ /**
3818
+ * Injected token-exchange `FetchLike` FACTORY (oauth design D2-a) — built per
3819
+ * provider in `bootstrap.ts` so the exchange carries a `{ providerId }` egress
3820
+ * ctx (per-provider proxy layer + upstream trace, bodies redacted); tests
3821
+ * inject a mock so no real token endpoint is hit. Mirrors how `login.ts`
3822
+ * injects its exchange fetch.
3823
+ */
3824
+ readonly oauthExchangeFetch: (providerId: SubscriptionProviderId) => FetchLike;
3825
+ /**
3826
+ * NARROW append handle (oauth design D2-a) — the OAuth complete handler needs
3827
+ * `appendProviderAccount` (NOT on the least-authority `SubscriptionTokenWriter`).
3828
+ * A minimal interface, NOT the full read-capable store, so no token-returning
3829
+ * read is reachable. Wired from the concrete `credentialStore` in `bootstrap.ts`.
3830
+ */
3831
+ readonly subscriptionAccountAppender: SubscriptionAccountAppender;
3832
+ /**
3833
+ * Codex interactive-OAuth flow store (app-parity-2 child 5). Tracks the async
3834
+ * loopback sign-in's polled status (token-free); only ONE codex login may be in
3835
+ * flight (port 1455 is one resource). Wired in `bootstrap.ts`.
3836
+ */
3837
+ readonly codexSessions: CodexOAuthSessionStore;
3838
+ /**
3839
+ * Kimi interactive-OAuth flow store (device code). Same token-free polled
3840
+ * shape as codex; one sign-in at a time. Wired in `bootstrap.ts`.
3841
+ */
3842
+ readonly kimiSessions: CodexOAuthSessionStore;
3843
+ /**
3844
+ * Grok interactive-OAuth flow store (device code). Same token-free polled
3845
+ * shape as codex; one sign-in at a time. Wired in `bootstrap.ts`.
3846
+ */
3847
+ readonly grokSessions: CodexOAuthSessionStore;
3848
+ /**
3849
+ * Copilot interactive-OAuth flow store (device code). Same token-free
3850
+ * polled shape; one sign-in at a time. Wired in `bootstrap.ts`.
3851
+ */
3852
+ readonly copilotSessions: CodexOAuthSessionStore;
3853
+ /** Antigravity interactive OAuth (loopback 51121) — the async flow store. */
3854
+ readonly antigravitySessions: CodexOAuthSessionStore;
3855
+ /** The one-shot 127.0.0.1:51121 listener (test seam like `codexAwaitLoopback`). */
3856
+ readonly antigravityAwaitLoopback: AntigravityLoopbackFn;
3857
+ /**
3858
+ * Resolve the ACTIVE antigravity account's access token for the dynamic
3859
+ * model-catalog probe (secret used daemon-side only; the route is
3860
+ * secret-free). Optional for older tests; absent → static-only catalog.
3861
+ */
3862
+ readonly resolveAntigravityAccessToken?: () => Promise<string | null>;
3863
+ /**
3864
+ * Codex loopback listener (app-parity-2 child 5) — defaults to `awaitLoopbackCode`
3865
+ * (binds 127.0.0.1:1455) in `bootstrap.ts`; tests inject a mock so no real port
3866
+ * is bound. The captured code crosses to the daemon ONLY (never the client).
3867
+ */
3868
+ readonly codexAwaitLoopback: CodexLoopbackFn;
3869
+ /**
3870
+ * Migration credential-store handle (app-parity child 6, design D2/D3). The
3871
+ * export gather needs `getFullConfig()` (full DECRYPTED tokens, in-memory only —
3872
+ * the pack is the only thing that leaves) and import needs `appendProviderAccount`
3873
+ * (multi-account append + re-encrypt at-rest). Confined to the migration
3874
+ * handlers (which seal/validate everything); never reached by a GET handler.
3875
+ * Wired from the concrete `credentialStore` in `bootstrap.ts`.
3876
+ */
3877
+ readonly migrationCredentialStore: MigrationCredentialStore;
3878
+ /**
3879
+ * Usage-stats query facade (usage-pricing child) — delegates to the JSONL
3880
+ * usage-event store. Aggregates only; carries no key material.
3881
+ */
3882
+ readonly usageRecorder: UsageRecorder;
3883
+ /** Pricing engine (upsert / source refresh / conflict resolution). */
3884
+ readonly pricingEngine: PricingEngine;
3885
+ /**
3886
+ * CONCRETE pricing store — ONLY for the store-local row `delete` (the core
3887
+ * `PricingStore` port is frozen; delete is a daemon-local extra).
3888
+ */
3889
+ readonly pricingStore: JsonPricingStore;
3890
+ /**
3891
+ * External-terminal opener for the Code CLI launch route (dashboard parity).
3892
+ * Optional — defaults to the real `defaultTerminalOpener`; tests inject a spy so
3893
+ * no terminal window is actually spawned.
3894
+ */
3895
+ readonly cliTerminalOpener?: TerminalOpener;
3896
+ /** Injectable PATH probe for CLI detection (tests fake "installed"). */
3897
+ readonly cliPathProbe?: PathProbe;
3898
+ /**
3899
+ * Injectable shell runner for the Code CLI install route. Optional — defaults
3900
+ * to the real `exec`-based runner; tests inject a stub so no package manager
3901
+ * actually runs.
3902
+ */
3903
+ readonly cliCommandRunner?: CommandRunner;
3904
+ /**
3905
+ * Codex command-auth helper invocation for KEY-SCOPED launches (the `--key-id`
3906
+ * variant). Wired by bootstrap from the same inputs as the integration
3907
+ * install's helper; absent ⇒ `keyId` launches answer 501 (light embedders).
3908
+ */
3909
+ readonly codexAuthHelper?: CodexAuthHelperConfig;
3910
+ /** Factory so each request observes the outbound server's current loopback port. */
3911
+ readonly integrationManagerFactory?: () => IntegrationManager;
3912
+ /**
3913
+ * search-settings-ui D3: the daemon's ONE search runtime plus its
3914
+ * bootstrap-captured frontend modes. Optional for lightweight embedders; the
3915
+ * standalone daemon wires it and the `/admin/api/search` diagnostics/test
3916
+ * routes return 501 when absent (the voucher/allowance optionality precedent).
3917
+ */
3918
+ readonly searchStatus?: SearchAdminRuntimeStatus;
3919
+ }
3920
+ /**
3921
+ * Dispatch one `/admin/api/*` request. `path` is the already-extracted pathname
3922
+ * (no query). The auth gate has already run in `AdminServer`.
3923
+ */
3924
+ declare function handleAdminApi(req: http.IncomingMessage, res: http.ServerResponse, path: string, deps: AdminApiDeps): Promise<void>;
3925
+
3926
+ /**
3927
+ * AdminServer — the daemon's localhost admin/dashboard HTTP listener (RT3,
3928
+ * design D1/D2).
3929
+ *
3930
+ * A SEPARATE `node:http` listener distinct from core's outbound `/v1/*` server
3931
+ * (port 8765, untouched). Mirrors `OutboundApiServer`'s proven shape:
3932
+ * - `http.createServer`, default bind `127.0.0.1` (or `0.0.0.0` when
3933
+ * `networkBinding`), `listen` with `EADDRINUSE`→ephemeral(port 0) fallback,
3934
+ * - `getStatus()` (running / bound port / dashboard URL), `start` / `stop`.
3935
+ * Default admin port 8766.
3936
+ *
3937
+ * Auth (design D2):
3938
+ * - Baseline = localhost bind, no token → reachable only from the machine.
3939
+ * - Optional `admin.token` → every `/admin/*` request (incl. `GET /`) must
3940
+ * carry `Authorization: Bearer <token>` or `X-Admin-Token: <token>`; compared
3941
+ * server-side with a constant-time equality → `401` on a miss.
3942
+ * - HARD SAFETY GATE: `networkBinding` (LAN/`0.0.0.0`) without a non-empty
3943
+ * `admin.token` → `start` REFUSES to bind (logs + stays down, fail closed).
3944
+ *
3945
+ * Routing: `GET /` (and `GET /admin`) → `302 /ui/`; `* /admin/api/*` → the
3946
+ * management API (`handleAdminApi`); `GET /ui[/...]` → the Control Panel static
3947
+ * UI (`handleUiStatic`, from `@omnicross/ui`); everything else → `404`.
3948
+ *
3949
+ * @module @omnicross/daemon/admin/AdminServer
3950
+ */
3951
+
3952
+ /** The dependencies the admin server + its API need (live daemon handles). */
3953
+ interface AdminServerDeps extends AdminApiDeps {
3954
+ /** Authenticated Codex rollout/state database manager. */
3955
+ codexSessionManager?: CodexSessionManager;
3956
+ /** Read the resolved admin config (enabled/port/networkBinding/token). */
3957
+ getAdminConfig: () => ResolvedAdminConfig;
3958
+ /**
3959
+ * Build the coarse, secret-free `/health` report (daemon-health-endpoint). A
3960
+ * shared closure over live handles (bootstrap wires the SAME builder into the
3961
+ * outbound server), served UNAUTHENTICATED — before the admin auth gate.
3962
+ */
3963
+ getHealthReport: () => HealthReport;
3964
+ /**
3965
+ * Injected logger (configurable-logging) — the admin listener's OWN lifecycle
3966
+ * lines (bind/refuse/error) route through it so they honor the configured
3967
+ * level / format / file sink.
3968
+ */
3969
+ logger: Logger;
3970
+ /**
3971
+ * OPTIONAL per-account probe-history reader (subscription-account-probe #8,
3972
+ * design D5). When wired (bootstrap → the `AccountHealthProbeScheduler`), the
3973
+ * AUTHED `GET /admin/api/account-probes` returns per-account probe history.
3974
+ * Absent ⇒ the route serves an empty list (byte-safe for embedders/tests that
3975
+ * do not wire it). Read-only + secret-free (ids + status labels, no tokens).
3976
+ */
3977
+ probeHistoryReader?: AccountProbeHistoryReader;
3978
+ /**
3979
+ * OPTIONAL audit query reader (request-audit-log, design D6). When wired
3980
+ * (bootstrap → the date-rotated store), the AUTHED `GET /admin/api/audit`
3981
+ * returns filtered records. Absent ⇒ the route serves an empty list. The
3982
+ * records carry IP/UA/bodies → this route is behind the auth gate ONLY, NEVER
3983
+ * unauthenticated, NEVER on `/health`.
3984
+ */
3985
+ auditReader?: AuditQueryReader;
3986
+ /** Metadata-only audit aggregate used by the overview error-rate metric. */
3987
+ auditStatsReader?: AuditStatsReader;
3988
+ /**
3989
+ * Reconstructs one record's bodies from the per-session shard store
3990
+ * (audit-store-sharding). Absent when audit was never enabled.
3991
+ */
3992
+ auditBodyReader?: AuditBodyReader;
3993
+ /** Runs cross-session body compaction on demand (audit-store-sharding D8). */
3994
+ auditCompactor?: AuditCompactor;
3995
+ /**
3996
+ * OPTIONAL billing delivery-status reader (billing-event-stream, design D5).
3997
+ * When wired (bootstrap → the ledger dir), the AUTHED `GET /admin/api/billing-status`
3998
+ * returns secret-free total/delivered/pending counts. Absent ⇒ zeroed counts.
3999
+ */
4000
+ billingStatusReader?: BillingStatusReader;
4001
+ }
4002
+ /** A live status snapshot for the admin listener. */
4003
+ interface AdminServerStatus {
4004
+ running: boolean;
4005
+ /** Actual bound port (0 when not running). */
4006
+ port: number;
4007
+ /** The dashboard URL (loopback or LAN base), or null when not running. */
4008
+ url: string | null;
4009
+ }
4010
+ declare class AdminServer {
4011
+ private readonly deps;
4012
+ private server;
4013
+ private boundPort;
4014
+ private boundAddr;
4015
+ /** Control Panel dist dir (resolved once at first request; null = no UI). */
4016
+ private uiDist;
4017
+ constructor(deps: AdminServerDeps);
4018
+ /**
4019
+ * Start the admin listener honoring the resolved admin config. Returns the
4020
+ * actual bound port, or `0` when it refuses/declines to bind (disabled or the
4021
+ * LAN fail-closed gate). Idempotent: a second call returns the bound port.
4022
+ */
4023
+ start(): Promise<number>;
4024
+ /** Bind once; on EADDRINUSE retry with an ephemeral port (port 0). */
4025
+ private listen;
4026
+ /** Per-request handler: auth gate (when a token is set) → routing. */
4027
+ private onRequest;
4028
+ private dispatch;
4029
+ /** Constant-time bearer/header check against the configured token. */
4030
+ private isAuthorized;
4031
+ /** Stop the listener and release the port. */
4032
+ stop(): Promise<void>;
4033
+ /** A live status snapshot. */
4034
+ getStatus(): AdminServerStatus;
3873
4035
  }
3874
4036
 
3875
4037
  interface ImageTemporaryBudgetStatus {
@@ -4185,6 +4347,7 @@ declare class JsonOutboundKeyDb implements OutboundKeyDb {
4185
4347
  outboundApiKeysSetEnabled(id: string, enabled: boolean): Promise<boolean>;
4186
4348
  outboundApiKeysSetPermissions(id: string, permissions: OutboundPermission[]): Promise<boolean>;
4187
4349
  outboundApiKeysSetMaxConcurrency(id: string, maxConcurrency: number | null): Promise<boolean>;
4350
+ outboundApiKeysSetUpstream(id: string, target: GatewayBindingTarget | null): Promise<boolean>;
4188
4351
  outboundApiKeysSetPolicy(id: string, policy: OutboundKeyPolicy): Promise<boolean>;
4189
4352
  outboundApiKeysMarkActivated(id: string, activatedAt: number): Promise<boolean>;
4190
4353
  /** Apply `fn` to the row with `id`, persisting when it returns true. */
@@ -4800,6 +4963,8 @@ interface DaemonPaths {
4800
4963
  imageRuntimeConfig?: AdminApiDeps['imageRuntimeConfig'];
4801
4964
  /** TEST/COMPOSITION SEAM: metadata-only Images status reader. */
4802
4965
  imageRuntimeStatus?: AdminApiDeps['imageRuntimeStatus'];
4966
+ /** TEST/COMPOSITION SEAM: explicitly-consuming Images live verifier. */
4967
+ imageLiveVerifier?: AdminApiDeps['imageLiveVerifier'];
4803
4968
  /** TEST/COMPOSITION SEAM: metadata-only successful Images config audit sink. */
4804
4969
  imageConfigAudit?: AdminApiDeps['imageConfigAudit'];
4805
4970
  /** TEST ONLY: deterministic Tier-A provider inside the production Images composition. */
@@ -4870,6 +5035,8 @@ interface Daemon {
4870
5035
  readonly usageRecorder: UsageRecorder;
4871
5036
  /** The localhost admin/dashboard HTTP listener (RT3). Started by `start.ts`. */
4872
5037
  readonly adminServer: AdminServer;
5038
+ /** Codex JSONL + state_5.sqlite session provider manager. */
5039
+ readonly codexSessionManager: CodexSessionManager;
4873
5040
  /**
4874
5041
  * Proactive background OAuth refresh sweep (external-cli-sync). NOT started
4875
5042
  * here — `start.ts` arms it for the resident daemon; the short-lived `launch`
@@ -5186,4 +5353,4 @@ declare function mapCcrToOmnicross(ccr: CcrConfig): {
5186
5353
  notes: string[];
5187
5354
  };
5188
5355
 
5189
- export { type AdminApiDeps, AdminServer, type AdminServerDeps, type AdminServerStatus, type CcrConfig, type CcrProvider, type CcrRouter, ConfigFileProviderConfigSource, ConfigurableLogger, ConsoleLogger, DEFAULT_ADMIN_PORT, type Daemon, type DaemonAdminConfig, type DaemonApiFormat, type DaemonConfig, type DaemonPaths, type DaemonProviderConfig, type HealthReportDeps, type HostedImageContributionFactory, type HostedImageRuntimeGenerationLease, type ImageApiMetricDimensions, type ImageApiMetricSnapshot, type ImageExecutionMetricDimensions, type ImageExecutionMetricSnapshot, type ImageHistogramSnapshot, ImageObservability, type ImageObservabilityOptions, type ImageObservabilitySnapshot, type ImageRuntimeCapabilityInspection, type ImageRuntimeGenerationFactoryOptions, type ImageRuntimeGenerationSharedStorage, ImageRuntimeManager, type ImageRuntimeManagerStatus, type ImageRuntimeMetadataObservability, type ImageRuntimeResourceStatus, type ImageRuntimeSafeUnavailableReason, JsonApiServerSettingsStore, JsonOutboundKeyDb, JsonSubscriptionCredentialStore, type PreparedImageRuntimeChange, type PreparedImageRuntimeGeneration, type ProductionImageRuntimeComponents, type ProductionImageRuntimeGeneration, type ResolvedAdminConfig, type TrustedImageApiRuntimeResolver, type TrustedImageApiRuntimeResolverOptions, buildDaemon, buildHealthReport, createHostedImageContributionFactory, createImageRuntimeGeneration, createTrustedImageApiRuntimeResolver, handleAdminApi, inferApiFormat, loadConfig, mapCcrToOmnicross, parseCcrConfig, resetDaemonSingletonsForTests, resolveAdminConfig, saveConfig, validateConfig };
5356
+ export { type AdminApiDeps, AdminServer, type AdminServerDeps, type AdminServerStatus, type ApplyCodexSessionProviderInput, type CcrConfig, type CcrProvider, type CcrRouter, type CodexSessionListResult, CodexSessionManager, CodexSessionManagerError, type CodexSessionManagerOptions, type CodexSessionProviderApplyResult, type CodexSessionProviderPlan, type CodexSessionProviderPreview, type CodexSessionSummary, type CodexStateDatabaseStatus, ConfigFileProviderConfigSource, ConfigurableLogger, ConsoleLogger, DEFAULT_ADMIN_PORT, type Daemon, type DaemonAdminConfig, type DaemonApiFormat, type DaemonConfig, type DaemonPaths, type DaemonProviderConfig, type HealthReportDeps, type HostedImageContributionFactory, type HostedImageRuntimeGenerationLease, type ImageApiMetricDimensions, type ImageApiMetricSnapshot, type ImageExecutionMetricDimensions, type ImageExecutionMetricSnapshot, type ImageHistogramSnapshot, ImageObservability, type ImageObservabilityOptions, type ImageObservabilitySnapshot, type ImageRuntimeCapabilityInspection, type ImageRuntimeGenerationFactoryOptions, type ImageRuntimeGenerationSharedStorage, ImageRuntimeManager, type ImageRuntimeManagerStatus, type ImageRuntimeMetadataObservability, type ImageRuntimeResourceStatus, type ImageRuntimeSafeUnavailableReason, JsonApiServerSettingsStore, JsonOutboundKeyDb, JsonSubscriptionCredentialStore, type PreparedImageRuntimeChange, type PreparedImageRuntimeGeneration, type ProductionImageRuntimeComponents, type ProductionImageRuntimeGeneration, type ResolvedAdminConfig, type TrustedImageApiRuntimeResolver, type TrustedImageApiRuntimeResolverOptions, buildDaemon, buildHealthReport, createHostedImageContributionFactory, createImageRuntimeGeneration, createTrustedImageApiRuntimeResolver, handleAdminApi, inferApiFormat, loadConfig, mapCcrToOmnicross, parseCcrConfig, replaceStructuredProviderFields, resetDaemonSingletonsForTests, resolveAdminConfig, saveConfig, validateConfig };