@omnicross/daemon 0.1.10 → 0.2.1

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,10 +1,10 @@
1
- import * as _omnicross_core from '@omnicross/core';
2
- import { OutboundApiServerConfig, Logger, ProviderConfigSource, TransformerService, Transformer, ResolvedTransformerChain, ApiServerSettingsStore, PricingStore, AutomaticPricingSource, OutboundKeyDb, OutboundKeyDbRow, OutboundKeyPolicy, PricingEngine as PricingEngine$1 } from '@omnicross/core';
1
+ import { Logger, OutboundApiServerConfig, ProviderConfigSource, TransformerService, Transformer, ResolvedTransformerChain, ApiServerSettingsStore, PricingStore, AutomaticPricingSource, OutboundPermission, OutboundKeyDb, OutboundKeyDbRow, OutboundKeyPolicy, PricingEngine as PricingEngine$1, OpenAIOperationRegistry } from '@omnicross/core';
2
+ import { SearchRuntime, SearchFrontendModes } from '@omnicross/core/search';
3
3
  import { ApiKeyPoolService } from '@omnicross/core/completion/ApiKeyPoolService';
4
- import { AllowanceSchedulingConfig, AccountProbeConfig, OutboundKeyDb as OutboundKeyDb$1, VoucherDb, KeySpendReader, OutboundApiServer } from '@omnicross/core/outbound-api';
4
+ import { AllowanceSchedulingConfig, AccountProbeConfig, OutboundKeyDb as OutboundKeyDb$1, VoucherDb, KeySpendReader, OutboundApiServer, ImagesServerConfig } 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
- import { SubscriptionCredentialStore, FetchLike, SubscriptionProviderRegistry, SubscriptionAccountService } from '@omnicross/subscriptions';
7
+ import { SubscriptionCredentialStore, FetchLike, CodexImageCapabilityEvidenceSource, CodexImageCapabilityEvidenceRequest, CodexImageCapabilityEvidence, CodexImageCapabilityObservation, ImageExecutionScheduler, ImageExecutionAccountKey, ImageExecutionSchedulerRequest, ImageExecutionSchedulerGrant, SubscriptionAccountService, SubscriptionProviderRegistry } from '@omnicross/subscriptions';
8
8
  import { AccountAllowanceSnapshot } from '@omnicross/contracts/account-allowance-types';
9
9
  import { ClaudeTokenConfig, CodexTokenConfig, GeminiTokenConfig, AccountTokensConfig, ProxyConfig, SubscriptionAccountSanitized, AccountClientIdentity, SubscriptionAccountEntry } from '@omnicross/contracts/account-tokens-types';
10
10
  import * as _omnicross_contracts_subscription_types from '@omnicross/contracts/subscription-types';
@@ -15,12 +15,17 @@ import { SubscriptionIdentityStore } from '@omnicross/core/provider-proxy/identi
15
15
  import { LoggingConfig, HealthReport } from '@omnicross/contracts/health-logging-types';
16
16
  import { SubscriptionAccountHealth } from '@omnicross/core/pipeline/SubscriptionAccountHealth';
17
17
  import { fetchUpstream } from '@omnicross/core/pipeline/upstreamFetch';
18
+ import { UsageTotals, ModelUsageRow, ApiKeyUsageRow } from '@omnicross/contracts/usage-stats-types';
18
19
  import { ThinkLevel } from '@omnicross/contracts/completion-types';
19
20
  import { AuditRecord, AuditStats, AuditBodyResult, AuditConfig } from '@omnicross/contracts/audit-types';
20
21
  import { BillingDeliveryStatus, BillingConfig, BillingEvent } from '@omnicross/contracts/billing-types';
21
22
  import http from 'node:http';
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';
22
26
  import { LLMProvider, AgentDefaultModels, GlobalModelParameters } from '@omnicross/contracts/llm-config';
23
27
  import { PricingEntry, PricingEntryInput, PricingResolution, PricingSourceRefreshResult } from '@omnicross/contracts/pricing-types';
28
+ import { ResponsesImageStateStore, ResponsesImageStateCommitInput, ResponsesImageCallBinding, ResponsesImageCallId, ResponsesImageCallResolution, ResponsesImageResponseResolution } from '@omnicross/core/image-generation/responses';
24
29
  import { WebhookConfig, WebhookEvent } from '@omnicross/contracts/webhook-types';
25
30
 
26
31
  /**
@@ -86,6 +91,193 @@ declare class SecretBox {
86
91
  encryptMaybe(value: string): string;
87
92
  }
88
93
 
94
+ /**
95
+ * usageRollup — the immutable per-day aggregate that lets a range query skip a
96
+ * day's rows entirely.
97
+ *
98
+ * A rollup is computed ONCE, when a day is closed (strictly before today's local
99
+ * date), and is then never rewritten. It is deliberately kept FOREVER, outliving
100
+ * the raw shard it was built from, for one load-bearing reason:
101
+ * `getSpendByKey().totalUsd` is a LIFETIME number that seeds the outbound key
102
+ * spend policy (`core/outbound-api/keySpendTracker`). Pruning raw rows without
103
+ * keeping a per-key aggregate behind would silently reset every key's lifetime
104
+ * spend and quietly re-open budgets the operator had already spent.
105
+ *
106
+ * WHAT IS EXACT AND WHAT IS NOT
107
+ *
108
+ * Every count, token sum and cost in a rollup is exact — it is a plain sum of
109
+ * the same rows a raw scan would have visited. The ONE approximation is
110
+ * `medianCacheHitRate`: a median cannot be composed from per-day medians, so
111
+ * each day stores a 1000-bin histogram of its per-event hit rates instead. The
112
+ * median recovered from bins is off by at most half a bin — 0.0005, i.e. well
113
+ * under a twentieth of a percentage point. Storing the raw rates instead would
114
+ * make a "permanent" sidecar grow with traffic, which is the property this whole
115
+ * layout exists to avoid.
116
+ *
117
+ * STALENESS: a rollup records the byte size of the shard it was built from.
118
+ * Because a shard is append-only, a size change means rows were added after the
119
+ * rollup was computed (only reachable by inserting into a past day — the tests
120
+ * do it, production does not) and the rollup is recomputed. A shard that is GONE
121
+ * is the pruned case, where the rollup is the sole surviving authority.
122
+ *
123
+ * @module @omnicross/daemon/usage/usageRollup
124
+ */
125
+
126
+ /** Everything in `UsageTotals` except the one field that cannot be summed. */
127
+ type SummableTotals = Omit<UsageTotals, 'medianCacheHitRate'>;
128
+ /** Per-model group inside a rollup. `unpriced` is deliberately absent — it is
129
+ * derived at query time from the injected pricing lookup, so a pricing change
130
+ * never requires rewriting an immutable rollup. */
131
+ type RollupModelRow = Omit<ModelUsageRow, 'unpriced'>;
132
+ /** Per-key group inside a rollup. `label` is absent for the same reason: it is
133
+ * resolved by the admin handler against the live key registry. */
134
+ type RollupApiKeyRow = Omit<ApiKeyUsageRow, 'label'>;
135
+ /** One hour of a day. Only hours that saw traffic are stored. */
136
+ interface RollupHourRow {
137
+ /** LOCAL hour of day, 0–23. */
138
+ hour: number;
139
+ requests: number;
140
+ inputTokens: number;
141
+ outputTokens: number;
142
+ cacheReadTokens: number;
143
+ cacheCreationTokens: number;
144
+ costUsd: number;
145
+ }
146
+ /** The on-disk `usage-YYYY-MM-DD.rollup.json` document. */
147
+ interface UsageDayRollup {
148
+ version: 1;
149
+ /** `YYYY-MM-DD`, LOCAL. Must match the file name. */
150
+ date: string;
151
+ /** Byte size of the shard this was built from; the staleness guard. */
152
+ sourceBytes: number;
153
+ totals: SummableTotals;
154
+ /** Sparse `binIndex -> count` over per-event cache-hit rates. */
155
+ hitRateBins: Record<string, number>;
156
+ byModel: RollupModelRow[];
157
+ byApiKey: RollupApiKeyRow[];
158
+ byHour: RollupHourRow[];
159
+ /** Distinct non-null session ids seen that day (drilldown index). */
160
+ sessionIds: string[];
161
+ /** True when `sessionIds` hit {@link SESSION_INDEX_LIMIT} and is incomplete. */
162
+ sessionIdsTruncated: boolean;
163
+ }
164
+
165
+ /**
166
+ * usageRollupStore — read-or-build the per-day rollup sidecars.
167
+ *
168
+ * A rollup is built LAZILY, the first time a query needs a closed day, and then
169
+ * reused forever. Building it is the only time that day's rows are ever parsed
170
+ * again; every subsequent query for any range containing that day reads a few KB
171
+ * of JSON instead of megabytes of JSONL.
172
+ *
173
+ * STALENESS is checked against the shard's byte size, not its mtime. Shards are
174
+ * append-only, so a size that no longer matches what the rollup was built from
175
+ * means rows were added afterwards — only reachable by inserting an event with a
176
+ * past `ts`, which the store API permits and the tests exercise. A shard that is
177
+ * absent entirely is the PRUNED case: the rollup is then the sole surviving
178
+ * record of that day and is trusted unconditionally.
179
+ *
180
+ * Writes are atomic (temp file + rename) so a crash mid-write cannot leave a
181
+ * half-JSON sidecar that later reads as a corrupt day.
182
+ *
183
+ * @module @omnicross/daemon/usage/usageRollupStore
184
+ */
185
+
186
+ declare class UsageRollupStore {
187
+ private readonly usageDir;
188
+ private readonly maxCached;
189
+ private readonly cache;
190
+ /** In-flight builds, so a burst of concurrent queries builds a day ONCE. */
191
+ private readonly inFlight;
192
+ constructor(usageDir: string, maxCached?: number);
193
+ /**
194
+ * The rollup for a CLOSED day, building it from the shard when absent or
195
+ * stale. `null` means the day has neither a usable rollup nor a shard — i.e.
196
+ * nothing was ever recorded for it.
197
+ *
198
+ * Callers must not ask for TODAY: today's rows are still arriving, so a rollup
199
+ * of them would be wrong the moment it was written.
200
+ */
201
+ get(dayKey: string, hasShard?: boolean): Promise<UsageDayRollup | null>;
202
+ /** Forget a day — used when an insert lands in an already-rolled-up day. */
203
+ invalidate(dayKey?: string): void;
204
+ /**
205
+ * Build (or rebuild) a day's rollup from its shard and persist it. Exposed for
206
+ * the prune sweeper, which must guarantee a rollup exists BEFORE it deletes
207
+ * the rows that rollup is derived from.
208
+ */
209
+ ensure(dayKey: string): Promise<UsageDayRollup | null>;
210
+ private resolve;
211
+ private readSidecar;
212
+ private writeSidecar;
213
+ private remember;
214
+ }
215
+
216
+ /**
217
+ * UsagePruneSweeper — retention for the sharded usage store.
218
+ *
219
+ * Prunes RAW day shards past `retentionDays` and NEVER touches a rollup. That
220
+ * asymmetry is the whole design: aggregates (totals, per-model, per-key, hourly
221
+ * trend) stay complete for the lifetime of the install, while the per-row detail
222
+ * that costs 5 MB a day is bounded. In particular `getSpendByKey().totalUsd` —
223
+ * the LIFETIME figure seeding the outbound key spend policy — keeps summing
224
+ * correctly across pruned days, which a naive "delete old rows" would silently
225
+ * zero, reopening budgets the operator had already spent.
226
+ *
227
+ * ORDERING IS LOAD-BEARING: a shard is only ever deleted AFTER its rollup exists
228
+ * on disk. If the rollup cannot be built, the shard is kept and the day is
229
+ * retried on the next tick. Deleting rows whose aggregate was never computed
230
+ * would destroy that day permanently.
231
+ *
232
+ * Shaped like `audit/AuditPruneSweeper`: `start()` arms an `unref()`ed interval
233
+ * plus one immediate pass, `dispose()` clears it, a re-entrancy guard prevents
234
+ * overlap, and nothing throws — a failed sweep is logged and retried, never
235
+ * fatal to the daemon.
236
+ *
237
+ * @module @omnicross/daemon/usage/UsagePruneSweeper
238
+ */
239
+
240
+ /** The `usage` config segment. */
241
+ interface UsageRetentionConfig {
242
+ /** Days of RAW rows to keep. `0` or absent ⇒ keep everything. */
243
+ retentionDays?: number;
244
+ }
245
+ declare class UsagePruneSweeper {
246
+ private readonly usageDir;
247
+ private readonly rollups;
248
+ private readonly logger;
249
+ private config;
250
+ private readonly intervalMs;
251
+ /** Injectable clock (ms) for deterministic tests. */
252
+ private readonly now;
253
+ private timer;
254
+ private sweeping;
255
+ constructor(usageDir: string, rollups: UsageRollupStore, logger: Logger, config?: UsageRetentionConfig, intervalMs?: number,
256
+ /** Injectable clock (ms) for deterministic tests. */
257
+ now?: () => number);
258
+ /** Whether raw rows are pruned at all. */
259
+ get enabled(): boolean;
260
+ /** Re-apply config to the live instance (boot + admin PUT hot-reload). */
261
+ configure(config: UsageRetentionConfig): void;
262
+ /**
263
+ * Arm the interval AND run one pass immediately (boot cleanup). No-op when
264
+ * retention is off (keep-everything is the zero-regression default).
265
+ * Idempotent.
266
+ */
267
+ start(): void;
268
+ /** Clear the interval (daemon shutdown / test teardown). Idempotent. */
269
+ dispose(): void;
270
+ /**
271
+ * One prune pass: for every day strictly OLDER than the retention cutoff that
272
+ * still has a shard, make sure its rollup exists and only then delete the
273
+ * shard. Exposed for tests; never throws. Returns the number of shards
274
+ * removed.
275
+ */
276
+ sweep(): Promise<number>;
277
+ /** The LOCAL-midnight epoch ms of the current day. */
278
+ private todayMidnight;
279
+ }
280
+
89
281
  /**
90
282
  * config.ts — the daemon's `config.json` schema + load/save (design D9).
91
283
  *
@@ -411,6 +603,12 @@ interface DaemonConfig {
411
603
  * so it is never walked by `decryptConfigSecrets`/`encryptConfigSecrets`.
412
604
  */
413
605
  logging?: LoggingConfig;
606
+ /**
607
+ * Optional usage-retention config. Absent ⇒ the default 90-day window on RAW
608
+ * usage rows. Per-day ROLLUPS are never pruned regardless — they carry the
609
+ * lifetime per-key spend the outbound key policy seeds from.
610
+ */
611
+ usage?: UsageRetentionConfig;
414
612
  }
415
613
  /** Apply defaults to a (possibly absent) admin block: enabled, port 8766,
416
614
  * loopback, no token. NOTE: an EXPLICIT `port: 0` is honored as "bind an
@@ -1545,6 +1743,254 @@ type AuditCompactor = () => {
1545
1743
  /** The read surface the AdminServer consumes (bootstrap binds it to the ledger dir). */
1546
1744
  type BillingStatusReader = () => BillingDeliveryStatus;
1547
1745
 
1746
+ type PreparedImageRuntimeGeneration = {
1747
+ readonly id: string;
1748
+ readonly enabled: true;
1749
+ readonly imageApi: ImageApiContributions;
1750
+ readonly hosted: ResponsesImageGenerationContribution;
1751
+ readonly hostedRuntime: HostedImageRuntimePolicy;
1752
+ readonly inspectCapability?: (apiKeyId: string) => Promise<Omit<ImageRuntimeCapabilityInspection, 'generationId'>>;
1753
+ readonly readRuntimeStatus?: () => ImageRuntimeResourceStatus;
1754
+ dispose(): void | Promise<void>;
1755
+ } | {
1756
+ readonly id: string;
1757
+ readonly enabled: false;
1758
+ dispose(): void | Promise<void>;
1759
+ };
1760
+ interface HostedImageRuntimeGenerationLease {
1761
+ readonly generationId: string;
1762
+ /** Compatibility/debug view; callers should prefer the deep methods below. */
1763
+ readonly contribution: ResponsesImageGenerationContribution;
1764
+ inspectRequest(input: ResponsesImageInspectionInput): ResponsesImageAdmission;
1765
+ validateSelection(admission: ResponsesImageAdmission, selection: ResponsesHostedToolSelection): void;
1766
+ openRequest(input: HostedImageOpenRequestInput): Promise<ResponsesImageRequestScope>;
1767
+ release(): Promise<void>;
1768
+ }
1769
+ interface HostedImageRuntimePolicy {
1770
+ readonly providerId: string;
1771
+ readonly imageModel: string;
1772
+ readonly referenceTtlMs: number;
1773
+ readonly maxOutputBytes: number;
1774
+ readonly maxTotalOutputBytes: number;
1775
+ readonly preferredAccountId?: string;
1776
+ readonly preferredAccountGroup?: string;
1777
+ readonly boundAccountFallbackPolicy?: 'strict' | 'pool';
1778
+ }
1779
+ interface HostedImageOpenRequestInput {
1780
+ readonly admission: ResponsesImageAdmission;
1781
+ readonly tenantId: string;
1782
+ readonly requestId: string;
1783
+ readonly sessionKey: string;
1784
+ readonly signal: AbortSignal;
1785
+ readonly authorizedPreviousResponseId?: string;
1786
+ /** Trusted affinity fact forwarded unchanged into the contribution scope. */
1787
+ readonly authorizedPreviousResponseKnownEmpty?: boolean;
1788
+ readonly mainProviderId: string;
1789
+ readonly selectedMainAccountId?: string;
1790
+ }
1791
+ /** Dormant integration seam for a later Native Responses owner. */
1792
+ interface HostedImageContributionFactory {
1793
+ acquire(): Promise<HostedImageRuntimeGenerationLease>;
1794
+ }
1795
+ /** Bind a stable factory to one app-session runtime manager without acquiring. */
1796
+ declare function createHostedImageContributionFactory(manager: ImageRuntimeManager): HostedImageContributionFactory;
1797
+ interface ImageRuntimeGenerationStatus {
1798
+ readonly generationId: string;
1799
+ readonly enabled: boolean;
1800
+ readonly httpLeases: number;
1801
+ readonly hostedLeases: number;
1802
+ }
1803
+ interface ImageRuntimeManagerStatus {
1804
+ readonly disposed: boolean;
1805
+ readonly current: ImageRuntimeGenerationStatus;
1806
+ readonly draining: readonly ImageRuntimeGenerationStatus[];
1807
+ }
1808
+ type ImageRuntimeSafeUnavailableReason = ImageCapabilityUnavailableReason | 'disabled' | 'runtime_unavailable';
1809
+ interface ImageRuntimeCapabilityInspection {
1810
+ readonly generationId: string;
1811
+ readonly enabled: boolean;
1812
+ readonly available: boolean;
1813
+ readonly providerId?: 'codex-subscription';
1814
+ readonly model?: string;
1815
+ readonly reason?: ImageRuntimeSafeUnavailableReason;
1816
+ readonly capabilities?: ImageCapabilities;
1817
+ }
1818
+ interface ImageRuntimeResourceStatus {
1819
+ readonly queue: Readonly<{
1820
+ activeJobs: number;
1821
+ waitingJobs: number;
1822
+ activeAccounts: number;
1823
+ waitingAccounts: number;
1824
+ waitingTenants: number;
1825
+ maxConcurrentJobsPerAccount: number;
1826
+ maxQueuedJobs: number;
1827
+ accepting: boolean;
1828
+ shuttingDown: boolean;
1829
+ }>;
1830
+ readonly temporary: Readonly<{
1831
+ activeScopes: number;
1832
+ totalBytes: number;
1833
+ tenantCount: number;
1834
+ maxActiveScopes: number;
1835
+ maxTotalBytes: number;
1836
+ maxTenantBytes: number;
1837
+ }>;
1838
+ readonly storage: Readonly<{
1839
+ mounts: number;
1840
+ retiredMounts: number;
1841
+ referenceEntries: number;
1842
+ referenceBytes: number;
1843
+ referenceTombstones: number;
1844
+ stateCalls: number;
1845
+ stateResponses: number;
1846
+ stateTombstones: number;
1847
+ pendingReferenceDeletes: number;
1848
+ maxReferenceEntries: number;
1849
+ maxReferenceBytes: number;
1850
+ maxTenantReferenceBytes: number;
1851
+ maxStateCalls: number;
1852
+ maxStateResponses: number;
1853
+ }>;
1854
+ }
1855
+ interface PreparedImageRuntimeChange {
1856
+ readonly generationId: string;
1857
+ publish(): void;
1858
+ rollback(): void;
1859
+ dispose(): Promise<void>;
1860
+ }
1861
+ /** App-session owner for stable forwarders and generation-pinned work. */
1862
+ declare class ImageRuntimeManager {
1863
+ #private;
1864
+ readonly contributions: ImageApiContributions;
1865
+ constructor(initial?: PreparedImageRuntimeGeneration);
1866
+ prepare(generation: PreparedImageRuntimeGeneration): PreparedImageRuntimeChange;
1867
+ acquireHosted(): Promise<HostedImageRuntimeGenerationLease>;
1868
+ inspectCapability(apiKeyId: string): Promise<ImageRuntimeCapabilityInspection>;
1869
+ listAvailableModels(apiKeyId: string): Promise<readonly string[]>;
1870
+ resourceStatus(): ImageRuntimeResourceStatus | undefined;
1871
+ status(): ImageRuntimeManagerStatus;
1872
+ dispose(): Promise<void>;
1873
+ }
1874
+
1875
+ type SafeProvider = 'codex-subscription' | 'unknown' | 'other';
1876
+ type SafeModel = 'gpt-image-2' | 'unknown' | 'other';
1877
+ type SafeErrorCode = ImageGenerationErrorCode | 'none' | 'other';
1878
+ type SafeCountOption = 'unknown' | '0' | '1' | '2-4' | '5+';
1879
+ type SafeBooleanOption = boolean | 'unknown';
1880
+ type SafeQuality = 'auto' | 'low' | 'medium' | 'high' | 'unknown' | 'other';
1881
+ type SafeBackground = 'auto' | 'opaque' | 'transparent' | 'unknown' | 'other';
1882
+ type SafeOutputFormat = 'png' | 'jpeg' | 'webp' | 'unknown' | 'other';
1883
+ declare const IMAGE_CONFIGURATION_AUDIT_FIELDS: readonly ["enablement", "provider", "model", "account", "queue", "temporary", "limits", "retention", "storage", "remote", "evidence"];
1884
+ type ImageConfigurationAuditField = typeof IMAGE_CONFIGURATION_AUDIT_FIELDS[number];
1885
+ /** Values are deliberately limited to safe categories and internal generation ids. */
1886
+ interface ImageConfigurationAuditRecord {
1887
+ readonly outcome: 'applied';
1888
+ readonly fields: readonly ImageConfigurationAuditField[];
1889
+ readonly previousGenerationId?: string;
1890
+ readonly generationId?: string;
1891
+ }
1892
+ interface ImageHistogramSnapshot {
1893
+ readonly count: number;
1894
+ readonly sum: number;
1895
+ /** Non-cumulative fixed buckets; `upperBound:null` is the overflow bucket. */
1896
+ readonly buckets: readonly {
1897
+ readonly upperBound: number | null;
1898
+ readonly count: number;
1899
+ }[];
1900
+ }
1901
+ interface ImageApiMetricDimensions {
1902
+ readonly endpoint: 'images.generate' | 'images.edit';
1903
+ readonly provider: SafeProvider;
1904
+ readonly model: SafeModel;
1905
+ readonly action: 'generate' | 'edit';
1906
+ readonly quality: SafeQuality;
1907
+ readonly background: SafeBackground;
1908
+ readonly outputFormat: SafeOutputFormat;
1909
+ readonly streaming: SafeBooleanOption;
1910
+ readonly requestedOutputs: SafeCountOption;
1911
+ readonly partialImages: SafeCountOption;
1912
+ readonly terminal: 'completed' | 'failed' | 'cancelled';
1913
+ readonly errorCode: SafeErrorCode;
1914
+ }
1915
+ interface ImageExecutionMetricDimensions {
1916
+ readonly provider: SafeProvider;
1917
+ readonly model: SafeModel;
1918
+ readonly action: 'generate' | 'edit' | 'other';
1919
+ readonly quality: SafeQuality;
1920
+ readonly background: SafeBackground;
1921
+ readonly outputFormat: SafeOutputFormat;
1922
+ readonly streaming: SafeBooleanOption;
1923
+ readonly requestedOutputs: SafeCountOption;
1924
+ readonly terminal: 'completed' | 'failed' | 'cancelled' | 'other';
1925
+ readonly errorCode: SafeErrorCode;
1926
+ }
1927
+ interface ImageApiMetricSnapshot {
1928
+ readonly dimensions: ImageApiMetricDimensions;
1929
+ readonly requests: number;
1930
+ readonly inputCount?: ImageHistogramSnapshot;
1931
+ readonly inputBytes?: ImageHistogramSnapshot;
1932
+ readonly referenceOutcomes: Readonly<{
1933
+ hits: number;
1934
+ notFound: number;
1935
+ expired: number;
1936
+ failed: number;
1937
+ }>;
1938
+ readonly cleanupOutcomes: Readonly<{
1939
+ completed: number;
1940
+ failed: number;
1941
+ }>;
1942
+ }
1943
+ interface ImageExecutionMetricSnapshot {
1944
+ readonly dimensions: ImageExecutionMetricDimensions;
1945
+ readonly executions: number;
1946
+ readonly finalLatencyMs: ImageHistogramSnapshot;
1947
+ readonly queueWaitMs?: ImageHistogramSnapshot;
1948
+ readonly generationDurationMs?: ImageHistogramSnapshot;
1949
+ readonly firstPartialLatencyMs?: ImageHistogramSnapshot;
1950
+ readonly inputCount: ImageHistogramSnapshot;
1951
+ readonly inputBytes: ImageHistogramSnapshot;
1952
+ readonly outputCount: ImageHistogramSnapshot;
1953
+ readonly outputBytes: ImageHistogramSnapshot;
1954
+ readonly retryCount?: ImageHistogramSnapshot;
1955
+ readonly authRefreshCount?: ImageHistogramSnapshot;
1956
+ readonly referenceSaveCount?: ImageHistogramSnapshot;
1957
+ readonly retentionRollbackFailures?: ImageHistogramSnapshot;
1958
+ }
1959
+ interface ImageObservabilitySnapshot {
1960
+ readonly apiRequests: readonly ImageApiMetricSnapshot[];
1961
+ readonly executions: readonly ImageExecutionMetricSnapshot[];
1962
+ readonly configurationChanges: readonly ImageConfigurationAuditRecord[];
1963
+ readonly overflow: Readonly<{
1964
+ apiRecords: number;
1965
+ telemetryRecords: number;
1966
+ }>;
1967
+ }
1968
+ interface ImageObservabilityOptions {
1969
+ /** Hard cap applied independently to API and execution dimension maps. */
1970
+ readonly maxDimensionSets?: number;
1971
+ }
1972
+ /** Process-local, metadata-only aggregation for HTTP and hosted Images work. */
1973
+ declare class ImageObservability {
1974
+ #private;
1975
+ readonly telemetrySink: ImageTelemetrySink;
1976
+ readonly audit: (record: ImageApiAuditRecord) => void;
1977
+ constructor(options?: ImageObservabilityOptions);
1978
+ recordApiAudit(record: ImageApiAuditRecord): void;
1979
+ recordTelemetry(record: ImageTelemetryRecord): void;
1980
+ recordConfigurationAudit(record: ImageConfigurationAuditRecord): void;
1981
+ snapshot(): ImageObservabilitySnapshot;
1982
+ reset(): void;
1983
+ }
1984
+
1985
+ interface PreparedServerConfigChange {
1986
+ /** Publish the prepared snapshot. Implementations should make this an infallible swap. */
1987
+ publish(): void | Promise<void>;
1988
+ /** Restore the exact runtime snapshot that preceded publish. */
1989
+ rollback(): void | Promise<void>;
1990
+ /** Release an unpublished or rolled-back replacement. */
1991
+ dispose(): void | Promise<void>;
1992
+ }
1993
+
1548
1994
  /**
1549
1995
  * autoDisableStore.ts — the daemon's PROCESS-IN-MEMORY auto-disable store.
1550
1996
  *
@@ -1684,9 +2130,16 @@ declare class ConfigFileProviderConfigSource implements ProviderConfigSource {
1684
2130
  * @module @omnicross/daemon/ports/JsonApiServerSettingsStore
1685
2131
  */
1686
2132
 
2133
+ interface JsonSettingsDocumentSnapshot {
2134
+ readonly existed: boolean;
2135
+ /** Raw persisted bytes; may contain encrypted secrets and must never enter a DTO or log. */
2136
+ readonly bytes?: Uint8Array;
2137
+ }
2138
+ type AtomicDocumentReplace = (targetPath: string, contents: Uint8Array) => void;
1687
2139
  declare class JsonApiServerSettingsStore implements ApiServerSettingsStore {
1688
2140
  private readonly configPath;
1689
2141
  private readonly box;
2142
+ private readonly atomicReplace;
1690
2143
  /**
1691
2144
  * @param configPath the daemon config.json whose `server` field is backed.
1692
2145
  * @param box OPTIONAL at-rest `SecretBox` (upstream-proxy). When set, the
@@ -1695,9 +2148,13 @@ declare class JsonApiServerSettingsStore implements ApiServerSettingsStore {
1695
2148
  * secret-aware — every OTHER server field is non-secret). Null
1696
2149
  * ⇒ passthrough (legacy/pure tests unchanged).
1697
2150
  */
1698
- constructor(configPath: string, box?: SecretBox | null);
2151
+ constructor(configPath: string, box?: SecretBox | null, atomicReplace?: AtomicDocumentReplace);
1699
2152
  get<T = unknown>(key: string): Promise<T | undefined>;
1700
2153
  set<T = unknown>(key: string, value: T): Promise<void>;
2154
+ /** Capture the exact prior document for an admin transaction rollback. */
2155
+ captureDocumentSnapshot(): JsonSettingsDocumentSnapshot;
2156
+ /** Restore exact prior bytes (including unrelated fields and encrypted secrets). */
2157
+ restoreDocumentSnapshot(snapshot: JsonSettingsDocumentSnapshot): void;
1701
2158
  /** Encrypt the proxy passwords + webhook + billing secrets before persisting (no-op without a box). */
1702
2159
  private encryptSecrets;
1703
2160
  /** Decrypt the proxy passwords + webhook + billing secrets on read (no-op without a box). */
@@ -1779,7 +2236,31 @@ declare class JsonPricingStore implements PricingStore {
1779
2236
  private replaceFile;
1780
2237
  }
1781
2238
 
2239
+ interface CodexAuthHelperConfig {
2240
+ command: string;
2241
+ args: string[];
2242
+ }
2243
+
1782
2244
  type IntegrationClientId = 'codex' | 'claude';
2245
+ type IntegrationKeyOwnership = 'managed' | 'selected';
2246
+ /** Secret-free pointer to an access key. The plaintext remains in the encrypted key store. */
2247
+ interface IntegrationKeyBinding {
2248
+ keyId: string;
2249
+ ownership: IntegrationKeyOwnership;
2250
+ }
2251
+ /** Redacted access-key state exposed by the integrations admin API. */
2252
+ interface IntegrationKeyBindingStatus {
2253
+ id: string;
2254
+ name: string;
2255
+ keyPrefix: string;
2256
+ ownership: IntegrationKeyOwnership;
2257
+ revealable: boolean;
2258
+ enabled: boolean;
2259
+ revoked: boolean;
2260
+ allowedEndpoints: OutboundPermission[];
2261
+ requiredEndpoints: OutboundPermission[];
2262
+ loopbackOnly: boolean;
2263
+ }
1783
2264
  type IntegrationStatusKind = 'not-installed' | 'enabled' | 'configuration-drift' | 'configuration-missing' | 'key-missing';
1784
2265
  interface IntegrationClientStatus {
1785
2266
  client: IntegrationClientId;
@@ -1788,6 +2269,8 @@ interface IntegrationClientStatus {
1788
2269
  installedAt?: number;
1789
2270
  gatewayBaseUrl?: string;
1790
2271
  message?: string;
2272
+ /** Selected key metadata only; never contains plaintext or the encrypted envelope. */
2273
+ key?: IntegrationKeyBindingStatus;
1791
2274
  }
1792
2275
  interface IntegrationChangePlan {
1793
2276
  client: IntegrationClientId;
@@ -1827,7 +2310,9 @@ interface IntegrationGatewayKeyRecord {
1827
2310
  }
1828
2311
  interface IntegrationState {
1829
2312
  version: 1;
2313
+ /** Legacy shared-key layout. New installs use `keyBindings`; retained for safe migration. */
1830
2314
  gatewayKey?: IntegrationGatewayKeyRecord;
2315
+ keyBindings?: Partial<Record<IntegrationClientId, IntegrationKeyBinding>>;
1831
2316
  clients: Partial<Record<IntegrationClientId, IntegrationInstallRecord>>;
1832
2317
  }
1833
2318
 
@@ -1845,27 +2330,38 @@ interface IntegrationManagerOptions {
1845
2330
  gatewayBaseUrl: string;
1846
2331
  keyDb: OutboundKeyDb;
1847
2332
  stateStore: IntegrationStateStore;
2333
+ codexAuthHelper?: CodexAuthHelperConfig;
1848
2334
  homeDir?: string;
1849
2335
  }
1850
- /** Coordinates a least-privilege gateway key with reversible native CLI config edits. */
2336
+ /** Coordinates per-client least-privilege keys with reversible native CLI config edits. */
1851
2337
  declare class IntegrationManager {
1852
2338
  private readonly options;
1853
2339
  private readonly homeDir;
2340
+ private readonly codexAuthHelper;
1854
2341
  constructor(options: IntegrationManagerOptions);
1855
2342
  listStatus(): Promise<IntegrationClientStatus[]>;
1856
2343
  plan(client: IntegrationClientId, configPath?: string): Promise<IntegrationChangePlan>;
1857
2344
  install(client: IntegrationClientId, configPath?: string): Promise<IntegrationClientStatus>;
1858
2345
  repair(client: IntegrationClientId): Promise<IntegrationClientStatus>;
1859
2346
  remove(client: IntegrationClientId): Promise<IntegrationClientStatus>;
2347
+ /** Bind a user-confirmed access key and grant only this client's required endpoints. */
2348
+ bindIntegrationKey(client: IntegrationClientId, keyId: string): Promise<IntegrationClientStatus>;
2349
+ /** Rotate every Omnicross-managed client binding; user-selected keys remain untouched. */
1860
2350
  rotateGatewayKey(): Promise<{
1861
- keyId: string;
2351
+ keyIds: Partial<Record<IntegrationClientId, string>>;
1862
2352
  }>;
1863
- getGatewayToken(): Promise<string>;
1864
- private ensureGatewayKey;
1865
- private isKeyUsable;
2353
+ /** Resolve the plaintext only for the command-auth helper; callers must not log it. */
2354
+ getIntegrationToken(client: IntegrationClientId): Promise<string>;
2355
+ /** Compatibility alias for callers predating per-client bindings. */
2356
+ getGatewayToken(client?: IntegrationClientId): Promise<string>;
2357
+ private ensureClientKey;
2358
+ private createManagedClientKey;
2359
+ private installedSecret;
2360
+ private rebindInstalledClient;
1866
2361
  private statusFor;
2362
+ private boundKeyDetails;
2363
+ private retireManagedKeys;
1867
2364
  private defaultConfigPath;
1868
- private codexAuthPathForConfig;
1869
2365
  private renderInstalled;
1870
2366
  }
1871
2367
 
@@ -1911,6 +2407,60 @@ type CommandRunner = (command: string) => Promise<{
1911
2407
  error?: string;
1912
2408
  }>;
1913
2409
 
2410
+ /**
2411
+ * searchAdminApi — the admin API's search surface (search-settings-ui D3 +
2412
+ * search-settings-tab D4).
2413
+ *
2414
+ * Three routes over the daemon's search state, dispatched from `adminApi.ts`'s
2415
+ * `case 'search'`:
2416
+ *
2417
+ * - `GET /admin/api/search/diagnostics` — a READ-ONLY, secret-free, network-free
2418
+ * snapshot: one row per provider the daemon can run (the ONE runtime's
2419
+ * descriptors, plus `unconfigured` rows for known API providers the persisted
2420
+ * config does not name — the doctor's classification), the effective frontend
2421
+ * modes, and the explicit apply semantics (codex immediate, rest restart).
2422
+ * - `POST /admin/api/search/test { providerId }` — ONE live fixed-query check on
2423
+ * a configured provider, classified by the doctor's pure functions. The
2424
+ * machine-facing health probe: it sends exactly `SEARCH_DOCTOR_QUERY`, never a
2425
+ * caller-supplied query, and never returns result content (plan §11.3 — its
2426
+ * contract is the automated doctor's fixed-query discipline).
2427
+ * - `POST /admin/api/search/query { providerId, query }` — the INTERACTIVE
2428
+ * channel for the settings page's per-provider test panel (owner feedback
2429
+ * 2026-09-02): ONE operator-typed query through ONE provider's contribution
2430
+ * built from the PERSISTED config, returning the doctor-classified diagnostic
2431
+ * PLUS the sanitized results. The two disciplines stay separate routes on
2432
+ * purpose: bending `/test` to accept a query would erase the boundary its
2433
+ * pinned tests and consumers depend on.
2434
+ *
2435
+ * SECRET SPINE (all three routes): no response ever carries a configured VALUE,
2436
+ * and a failure response carries only the doctor's SANITIZED error shape — raw
2437
+ * upstream error bodies (which may quote the stored key) never serialize. The
2438
+ * query endpoint additionally sanitizes every returned result field BEFORE
2439
+ * serialization (plan §11.1: search results are untrusted input), and the
2440
+ * operator's query is never logged anywhere.
2441
+ *
2442
+ * The diagnostics dep is OPTIONAL (`AdminApiDeps.searchStatus`): light embedders
2443
+ * that wire no search runtime get 501 for all routes (the voucher/allowance
2444
+ * optionality precedent) rather than a fabricated snapshot.
2445
+ *
2446
+ * @module @omnicross/daemon/admin/searchAdminApi
2447
+ */
2448
+
2449
+ /**
2450
+ * The daemon search state the admin surface needs. Structurally satisfied by
2451
+ * what `bootstrap.ts` already holds (the ONE runtime + its captured modes);
2452
+ * `testFetch` is a TEST SEAM so route tests can intercept the one live probe
2453
+ * without any network.
2454
+ */
2455
+ interface SearchAdminRuntimeStatus {
2456
+ /** The daemon's ONE assembled search runtime (provider descriptors). */
2457
+ readonly runtime: SearchRuntime;
2458
+ /** Modes as captured at bootstrap (responses/anthropic are these, live). */
2459
+ readonly modes: SearchFrontendModes;
2460
+ /** TEST SEAM: fetch primitive for the live-test probe. Absent ⇒ real transport. */
2461
+ readonly testFetch?: (url: string, init: RequestInit) => Promise<Response>;
2462
+ }
2463
+
1914
2464
  /**
1915
2465
  * migration.ts — the export gather + import apply logic for the passphrase pack
1916
2466
  * (app-parity child 6, design D2/D3/D5).
@@ -1985,6 +2535,11 @@ interface PoolKeyHealth {
1985
2535
  interface PoolHealthReader {
1986
2536
  getKeyHealth(providerId: string): Promise<Record<string, PoolKeyHealth>>;
1987
2537
  }
2538
+ interface AdminImagesStatusReader {
2539
+ inspectCapability(apiKeyId: string): Promise<ImageRuntimeCapabilityInspection>;
2540
+ status(): ImageRuntimeManagerStatus;
2541
+ resourceStatus(): ImageRuntimeResourceStatus | undefined;
2542
+ }
1988
2543
  /** The live daemon handles the management API operates over. */
1989
2544
  interface AdminApiDeps {
1990
2545
  /** Path to the daemon's `config.json` (provider catalog + `server` field). */
@@ -2009,6 +2564,16 @@ interface AdminApiDeps {
2009
2564
  readonly settingsStore: JsonApiServerSettingsStore;
2010
2565
  /** The running outbound server (status + live applyConfig). */
2011
2566
  readonly outboundApiServer: OutboundApiServer;
2567
+ /** True only when production composed the hardened per-hop remote resolver. */
2568
+ readonly imageRemoteResolverAvailable?: boolean;
2569
+ /** Optional production Images runtime generation participant. */
2570
+ readonly imageRuntimeConfig?: {
2571
+ prepareConfig(config: ImagesServerConfig): Promise<PreparedServerConfigChange>;
2572
+ };
2573
+ /** Narrow metadata-only reader for authenticated Images capability/status. */
2574
+ readonly imageRuntimeStatus?: AdminImagesStatusReader;
2575
+ /** Metadata-only successful Images configuration audit sink. */
2576
+ readonly imageConfigAudit?: (record: ImageConfigurationAuditRecord) => void;
2012
2577
  /** Process-local machine-managed routing leases (optional for light embedders). */
2013
2578
  readonly routeLeaseManager?: RouteLeaseManager;
2014
2579
  /** Subscription accounts (token-free `listAll`). */
@@ -2117,6 +2682,13 @@ interface AdminApiDeps {
2117
2682
  readonly cliCommandRunner?: CommandRunner;
2118
2683
  /** Factory so each request observes the outbound server's current loopback port. */
2119
2684
  readonly integrationManagerFactory?: () => IntegrationManager;
2685
+ /**
2686
+ * search-settings-ui D3: the daemon's ONE search runtime plus its
2687
+ * bootstrap-captured frontend modes. Optional for lightweight embedders; the
2688
+ * standalone daemon wires it and the `/admin/api/search` diagnostics/test
2689
+ * routes return 501 when absent (the voucher/allowance optionality precedent).
2690
+ */
2691
+ readonly searchStatus?: SearchAdminRuntimeStatus;
2120
2692
  }
2121
2693
  /**
2122
2694
  * Dispatch one `/admin/api/*` request. `path` is the already-extracted pathname
@@ -2233,9 +2805,673 @@ declare class AdminServer {
2233
2805
  getStatus(): AdminServerStatus;
2234
2806
  }
2235
2807
 
2808
+ type DaemonImagePathArea = 'temporary' | 'artifacts' | 'state' | 'evidence' | 'mountManifest';
2809
+ interface DaemonImagePaths {
2810
+ readonly applicationDataRoot: string;
2811
+ readonly imagesRoot: string;
2812
+ readonly temporaryRoot: string;
2813
+ readonly durableRoot: string;
2814
+ readonly artifactsRoot: string;
2815
+ readonly stateRoot: string;
2816
+ readonly evidenceRoot: string;
2817
+ readonly mountManifestRoot: string;
2818
+ readonly mountManifestPath: string;
2819
+ }
2820
+ interface ImageRootValidationOptions {
2821
+ readonly label?: string;
2822
+ readonly processDirectory?: string;
2823
+ readonly userHome?: string;
2824
+ readonly temporaryDirectory?: string;
2825
+ }
2826
+ interface CreateDaemonImagePathResolverOptions extends ImageRootValidationOptions {
2827
+ readonly configPath: string;
2828
+ readonly storageRoot?: string;
2829
+ }
2830
+ interface VerifiedDaemonImagePath {
2831
+ readonly area: DaemonImagePathArea;
2832
+ readonly absolutePath: string;
2833
+ readonly kind: 'opaque-file' | 'opaque-directory' | 'mount-manifest';
2834
+ }
2835
+ /**
2836
+ * Owns all daemon Images filesystem names. Callers receive opaque capabilities,
2837
+ * never a filename-accepting delete primitive; destructive methods revalidate
2838
+ * the root identity, descendant relationship, basename, and symlink state.
2839
+ */
2840
+ declare class DaemonImagePathResolver {
2841
+ #private;
2842
+ readonly paths: DaemonImagePaths;
2843
+ constructor(options: CreateDaemonImagePathResolverOptions);
2844
+ createOpaqueFile(area: Exclude<DaemonImagePathArea, 'temporary' | 'mountManifest'>, format?: 'bin' | 'json' | 'tmp'): VerifiedDaemonImagePath;
2845
+ createOpaqueDirectory(area?: Exclude<DaemonImagePathArea, 'mountManifest'>): VerifiedDaemonImagePath;
2846
+ mountManifest(): VerifiedDaemonImagePath;
2847
+ /** Revalidate and return one internal root for store-local bounded I/O. */
2848
+ verifiedRoot(area: DaemonImagePathArea): string;
2849
+ /** Revalidate immediately before unlinking a resolver-issued file capability. */
2850
+ removeFile(target: VerifiedDaemonImagePath): void;
2851
+ /** Only empty opaque directories may be removed until the owned-marker layer is composed. */
2852
+ removeEmptyDirectory(target: VerifiedDaemonImagePath): void;
2853
+ private issue;
2854
+ private verifyDestructiveTarget;
2855
+ }
2856
+
2857
+ interface FileCodexImageCapabilityEvidenceManifestOwnerOptions {
2858
+ readonly paths: DaemonImagePathResolver;
2859
+ readonly maxEntries?: number;
2860
+ readonly now?: () => number;
2861
+ readonly random?: (bytes: number) => Buffer;
2862
+ readonly hmacSalt?: Uint8Array;
2863
+ readonly replaceManifest?: (targetPath: string, contents: Uint8Array) => void;
2864
+ }
2865
+ type FileCodexImageCapabilityEvidenceSourceOptions = Readonly<(FileCodexImageCapabilityEvidenceManifestOwnerOptions & {
2866
+ readonly ttlMs: number;
2867
+ }) | {
2868
+ readonly owner: FileCodexImageCapabilityEvidenceManifestOwner;
2869
+ readonly ttlMs: number;
2870
+ }>;
2871
+ interface FileCodexImageCapabilityEvidenceStatus {
2872
+ readonly entries: number;
2873
+ readonly freshEntries: number;
2874
+ readonly staleEntries: number;
2875
+ readonly bytes: number;
2876
+ }
2877
+ /** Revision-aware manifest owner shared by runtime generations, doctor, and cleanup. */
2878
+ declare class FileCodexImageCapabilityEvidenceManifestOwner {
2879
+ #private;
2880
+ constructor(options: FileCodexImageCapabilityEvidenceManifestOwnerOptions);
2881
+ createSource(ttlMs: number): FileCodexImageCapabilityEvidenceSource;
2882
+ resolveWithTtl(request: CodexImageCapabilityEvidenceRequest, ttlMs: number): Promise<CodexImageCapabilityEvidence>;
2883
+ recordSuccessfulVerificationWithTtl(observation: CodexImageCapabilityObservation, ttlMs: number): Promise<void>;
2884
+ cleanup(now: number, limit: number): Promise<{
2885
+ readonly entriesRemoved: number;
2886
+ readonly bytesRemoved: number;
2887
+ }>;
2888
+ statusWithTtl(ttlMs: number): FileCodexImageCapabilityEvidenceStatus;
2889
+ }
2890
+ /** Immutable TTL view over a revision-aware file-backed evidence manifest owner. */
2891
+ declare class FileCodexImageCapabilityEvidenceSource implements CodexImageCapabilityEvidenceSource {
2892
+ #private;
2893
+ constructor(options: FileCodexImageCapabilityEvidenceSourceOptions);
2894
+ createView(ttlMs: number): FileCodexImageCapabilityEvidenceSource;
2895
+ resolve(request: CodexImageCapabilityEvidenceRequest): Promise<CodexImageCapabilityEvidence>;
2896
+ recordSuccessfulVerification(observation: CodexImageCapabilityObservation): Promise<void>;
2897
+ cleanup(now: number, limit: number): Promise<{
2898
+ readonly entriesRemoved: number;
2899
+ readonly bytesRemoved: number;
2900
+ }>;
2901
+ status(): FileCodexImageCapabilityEvidenceStatus;
2902
+ ttlMs(): number;
2903
+ /** Lifecycle-symmetric no-op; physical safety no longer depends on local leases. */
2904
+ dispose(): void;
2905
+ }
2906
+
2907
+ interface FileImageReferenceStoreLimits {
2908
+ readonly ttlMs: number;
2909
+ readonly maxArtifactBytes: number;
2910
+ readonly maxTotalBytes: number;
2911
+ readonly maxTenantBytes: number;
2912
+ readonly maxEntries: number;
2913
+ readonly maxTombstones: number;
2914
+ readonly tombstoneTtlMs: number;
2915
+ }
2916
+ interface FileImageReferenceStoreOptions {
2917
+ readonly paths: DaemonImagePathResolver;
2918
+ readonly limits: FileImageReferenceStoreLimits;
2919
+ readonly secretBox?: SecretBox;
2920
+ readonly now?: () => number;
2921
+ readonly random?: (bytes: number) => Buffer;
2922
+ readonly replaceManifest?: (targetPath: string, contents: Uint8Array) => void;
2923
+ }
2924
+ interface FileImageReferenceReconciliationResult {
2925
+ readonly metadataRemoved: number;
2926
+ readonly metadataDegradedToProviderReference: number;
2927
+ readonly orphanFilesRemoved: number;
2928
+ readonly incompleteFilesRemoved: number;
2929
+ readonly invalidDescendants: number;
2930
+ }
2931
+ declare class FileImageReferenceStore implements ImageReferenceStore {
2932
+ #private;
2933
+ constructor(options: FileImageReferenceStoreOptions);
2934
+ save(input: ImageReferenceSaveInput): Promise<ImageReferenceMetadata>;
2935
+ /** Generation-bound write entry point; reads and maintenance remain shared. */
2936
+ saveWithLimits(input: ImageReferenceSaveInput, limits: FileImageReferenceStoreLimits): Promise<ImageReferenceMetadata>;
2937
+ /** Updates only app-session maintenance policy; pinned writes pass their own limits. */
2938
+ updateMaintenanceLimits(limits: FileImageReferenceStoreLimits): void;
2939
+ resolve(tenantId: string, referenceId: ImageReferenceId): Promise<ImageReferenceResolution>;
2940
+ delete(tenantId: string, referenceId: ImageReferenceId): Promise<boolean>;
2941
+ /** Daemon-internal cleanup path; accepts only the local reference-domain tenant HMAC. */
2942
+ deleteByHashedTenantKey(tenantKey: string, referenceId: ImageReferenceId): Promise<boolean>;
2943
+ cleanup(now?: number): Promise<number>;
2944
+ status(): {
2945
+ readonly entries: number;
2946
+ readonly bytes: number;
2947
+ readonly tombstones: number;
2948
+ };
2949
+ hasLiveReferenceByHashedTenantKey(tenantKey: string, referenceId: ImageReferenceId, now?: number): Promise<boolean>;
2950
+ reconcileOwnedFiles(maxEntries: number): Promise<FileImageReferenceReconciliationResult>;
2951
+ openArtifact(fileName: string, byteLength: number, signal?: AbortSignal): Promise<ReadableStream<Uint8Array>>;
2952
+ private exclusive;
2953
+ private tenantKey;
2954
+ private newReferenceId;
2955
+ private validateSaveInput;
2956
+ private selectVictims;
2957
+ private nextTombstones;
2958
+ private writeArtifact;
2959
+ private artifactPath;
2960
+ private validArtifact;
2961
+ private removeArtifact;
2962
+ private safeUnlinkArtifactPath;
2963
+ private releaseLease;
2964
+ private manifestPath;
2965
+ private persist;
2966
+ private atomicReplace;
2967
+ private loadManifest;
2968
+ }
2969
+
2970
+ interface FileResponsesImageStateStoreLimits {
2971
+ readonly maxCalls: number;
2972
+ readonly maxResponses: number;
2973
+ readonly maxTombstones: number;
2974
+ readonly tombstoneTtlMs: number;
2975
+ }
2976
+ interface FileResponsesImageStateStoreOptions {
2977
+ readonly paths: DaemonImagePathResolver;
2978
+ readonly limits: FileResponsesImageStateStoreLimits;
2979
+ readonly now?: () => number;
2980
+ readonly random?: (bytes: number) => Buffer;
2981
+ readonly replaceManifest?: (targetPath: string, contents: Uint8Array) => void;
2982
+ }
2983
+ interface PendingResponsesImageReferenceDelete {
2984
+ readonly referenceTenantKey: string;
2985
+ readonly binding: ResponsesImageCallBinding;
2986
+ }
2987
+ /** Durable production implementation of the existing Responses image-state contract. */
2988
+ declare class FileResponsesImageStateStore implements ResponsesImageStateStore {
2989
+ #private;
2990
+ constructor(options: FileResponsesImageStateStoreOptions);
2991
+ commit(input: ResponsesImageStateCommitInput): Promise<readonly ResponsesImageCallBinding[]>;
2992
+ /** Generation-bound write entry point; reads and maintenance remain shared. */
2993
+ commitWithLimits(input: ResponsesImageStateCommitInput, limits: FileResponsesImageStateStoreLimits): Promise<readonly ResponsesImageCallBinding[]>;
2994
+ /** Updates only app-session maintenance policy; pinned commits pass their own limits. */
2995
+ updateMaintenanceLimits(limits: FileResponsesImageStateStoreLimits): void;
2996
+ resolveCall(tenantId: string, callId: ResponsesImageCallId): Promise<ResponsesImageCallResolution>;
2997
+ resolveResponse(tenantId: string, responseId: string): Promise<ResponsesImageResponseResolution>;
2998
+ deleteCall(tenantId: string, callId: ResponsesImageCallId): Promise<ResponsesImageCallBinding | undefined>;
2999
+ deleteResponse(tenantId: string, responseId: string): Promise<boolean>;
3000
+ cleanup(now?: number): Promise<readonly ResponsesImageCallBinding[]>;
3001
+ pendingReferenceDeletes(limit?: number): readonly PendingResponsesImageReferenceDelete[];
3002
+ acknowledgeReferenceDeletes(completed: readonly PendingResponsesImageReferenceDelete[]): Promise<number>;
3003
+ reconcileBrokenReferenceLinks(hasLiveReference: (referenceTenantKey: string, referenceId: ResponsesImageCallBinding['referenceId']) => Promise<boolean>, maxEntries: number): Promise<readonly ResponsesImageCallBinding[]>;
3004
+ status(): {
3005
+ readonly calls: number;
3006
+ readonly responses: number;
3007
+ readonly tombstones: number;
3008
+ readonly pendingReferenceDeletes: number;
3009
+ };
3010
+ private exclusive;
3011
+ private failure;
3012
+ private assertCommit;
3013
+ private tenantKey;
3014
+ private rememberTombstone;
3015
+ private enqueuePendingReferenceDelete;
3016
+ private hasTombstone;
3017
+ private prunedTombstones;
3018
+ private pruneTombstonesInPlace;
3019
+ private sameTombstones;
3020
+ private touch;
3021
+ private releaseCall;
3022
+ private releaseResponse;
3023
+ private manifestPath;
3024
+ private persist;
3025
+ private atomicReplace;
3026
+ private loadManifest;
3027
+ }
3028
+
3029
+ interface ImageStorageMountBackend {
3030
+ readonly id: string;
3031
+ readonly createdAt: number;
3032
+ readonly resolver: DaemonImagePathResolver;
3033
+ readonly references: FileImageReferenceStore;
3034
+ readonly responsesState: FileResponsesImageStateStore;
3035
+ }
3036
+ interface ImageStorageMountCatalogOptions {
3037
+ readonly pathOptions: Omit<CreateDaemonImagePathResolverOptions, 'storageRoot'>;
3038
+ readonly activeStorageRoot?: string;
3039
+ readonly referenceLimits: FileImageReferenceStoreLimits;
3040
+ readonly responsesStateLimits: FileResponsesImageStateStoreLimits;
3041
+ readonly secretBox?: SecretBox;
3042
+ readonly now?: () => number;
3043
+ readonly random?: (bytes: number) => Buffer;
3044
+ readonly replaceCatalog?: (targetPath: string, contents: Uint8Array) => void;
3045
+ readonly reconcileCorruptManifests?: boolean;
3046
+ }
3047
+ interface ImageStorageMountPolicy {
3048
+ readonly referenceLimits: FileImageReferenceStoreLimits;
3049
+ readonly responsesStateLimits: FileResponsesImageStateStoreLimits;
3050
+ }
3051
+ interface PreparedImageStorageMountActivation {
3052
+ readonly backend: ImageStorageMountBackend;
3053
+ publish(): ImageStorageMountBackend;
3054
+ rollback(): void;
3055
+ dispose(): void;
3056
+ }
3057
+ /** Owns the durable-root set independently from any one runtime generation. */
3058
+ declare class ImageStorageMountCatalog {
3059
+ #private;
3060
+ constructor(options: ImageStorageMountCatalogOptions);
3061
+ active(): ImageStorageMountBackend;
3062
+ mountsForRead(): readonly ImageStorageMountBackend[];
3063
+ status(): {
3064
+ readonly mounts: number;
3065
+ readonly retiredMounts: number;
3066
+ };
3067
+ startupReconciliationStatus(): {
3068
+ readonly corruptManifestsQuarantined: number;
3069
+ };
3070
+ utilization(): {
3071
+ readonly referenceEntries: number;
3072
+ readonly referenceBytes: number;
3073
+ readonly referenceTombstones: number;
3074
+ readonly stateCalls: number;
3075
+ readonly stateResponses: number;
3076
+ readonly stateTombstones: number;
3077
+ readonly pendingReferenceDeletes: number;
3078
+ };
3079
+ /** Pins a backend object until its owning runtime generation drains. */
3080
+ retainBackend(backend: ImageStorageMountBackend): () => void;
3081
+ activate(storageRoot?: string): ImageStorageMountBackend;
3082
+ /** Prepare a validated backend without changing the catalog's active mount. */
3083
+ prepareActivation(storageRoot?: string, policy?: ImageStorageMountPolicy): PreparedImageStorageMountActivation;
3084
+ retireEmptyMount(mountId: string): boolean;
3085
+ private createResolver;
3086
+ private createBackend;
3087
+ private applyMaintenancePolicy;
3088
+ private newMountId;
3089
+ private isManifestError;
3090
+ private quarantineManifest;
3091
+ private isVerifiedEmpty;
3092
+ private catalogPath;
3093
+ private persist;
3094
+ private atomicReplace;
3095
+ private loadCatalog;
3096
+ }
3097
+ declare class MountedImageReferenceStore implements ImageReferenceStore {
3098
+ private readonly catalog;
3099
+ private readonly writeBackend?;
3100
+ private readonly writeLimits?;
3101
+ constructor(catalog: ImageStorageMountCatalog, writeBackend?: ImageStorageMountBackend | undefined, writeLimits?: FileImageReferenceStoreLimits | undefined);
3102
+ bindWriteBackend(backend: ImageStorageMountBackend, limits: FileImageReferenceStoreLimits): MountedImageReferenceStore;
3103
+ status(): Readonly<{
3104
+ referenceEntries: number;
3105
+ referenceBytes: number;
3106
+ referenceTombstones: number;
3107
+ stateCalls: number;
3108
+ stateResponses: number;
3109
+ stateTombstones: number;
3110
+ pendingReferenceDeletes: number;
3111
+ mounts: number;
3112
+ retiredMounts: number;
3113
+ }>;
3114
+ save(input: ImageReferenceSaveInput): Promise<_omnicross_contracts_image_generation_types.ImageReferenceMetadata>;
3115
+ resolve(tenantId: string, referenceId: ImageReferenceId): Promise<ImageReferenceResolution>;
3116
+ delete(tenantId: string, referenceId: ImageReferenceId): Promise<boolean>;
3117
+ deleteByHashedTenantKey(tenantKey: string, referenceId: ImageReferenceId): Promise<boolean>;
3118
+ cleanup(now?: number): Promise<number>;
3119
+ }
3120
+ declare class MountedResponsesImageStateStore implements ResponsesImageStateStore {
3121
+ private readonly catalog;
3122
+ private readonly writeBackend?;
3123
+ private readonly writeLimits?;
3124
+ constructor(catalog: ImageStorageMountCatalog, writeBackend?: ImageStorageMountBackend | undefined, writeLimits?: FileResponsesImageStateStoreLimits | undefined);
3125
+ bindWriteBackend(backend: ImageStorageMountBackend, limits: FileResponsesImageStateStoreLimits): MountedResponsesImageStateStore;
3126
+ commit(input: ResponsesImageStateCommitInput): Promise<readonly ResponsesImageCallBinding[]>;
3127
+ resolveCall(tenantId: string, callId: ResponsesImageCallId): Promise<ResponsesImageCallResolution>;
3128
+ resolveResponse(tenantId: string, responseId: string): Promise<ResponsesImageResponseResolution>;
3129
+ deleteCall(tenantId: string, callId: ResponsesImageCallId): Promise<ResponsesImageCallBinding | undefined>;
3130
+ deleteResponse(tenantId: string, responseId: string): Promise<boolean>;
3131
+ cleanup(now?: number): Promise<readonly ResponsesImageCallBinding[]>;
3132
+ }
3133
+
3134
+ interface ImageDoctorLocalSnapshot {
3135
+ readonly config: Readonly<{
3136
+ enabled: boolean;
3137
+ provider: 'codex-subscription';
3138
+ model: string;
3139
+ valid: boolean;
3140
+ errorCount: number;
3141
+ }>;
3142
+ readonly roots: Readonly<{
3143
+ valid: boolean;
3144
+ verifiedAreas: number;
3145
+ expectedAreas: number;
3146
+ }>;
3147
+ readonly stores: Readonly<{
3148
+ valid: boolean;
3149
+ mounts: number;
3150
+ retiredMounts: number;
3151
+ referenceEntries: number;
3152
+ referenceBytes: number;
3153
+ stateCalls: number;
3154
+ stateResponses: number;
3155
+ corruptManifestsQuarantined: number;
3156
+ }>;
3157
+ readonly permissions: Readonly<{
3158
+ valid: boolean;
3159
+ rows: number;
3160
+ legacyRows: number;
3161
+ invalidRows: number;
3162
+ imagesAuthorizedRows: number;
3163
+ }>;
3164
+ readonly account: Readonly<{
3165
+ present: boolean;
3166
+ usable: boolean;
3167
+ reason: 'ready' | 'missing' | 'unavailable';
3168
+ }>;
3169
+ readonly evidence: Readonly<FileCodexImageCapabilityEvidenceStatus & {
3170
+ valid: boolean;
3171
+ }>;
3172
+ }
3173
+ type ImageDoctorLiveFailureCode = 'images_disabled' | 'codex_account_unavailable' | 'evidence_store_unavailable' | 'evidence_persist_failed' | ImageGenerationErrorCode;
3174
+ type ImageDoctorLiveResult = Readonly<{
3175
+ ok: true;
3176
+ code: 'verified';
3177
+ model: 'gpt-image-2';
3178
+ quality: 'low';
3179
+ outputFormat: 'png';
3180
+ freshEvidenceEntries: number;
3181
+ }> | Readonly<{
3182
+ ok: false;
3183
+ code: ImageDoctorLiveFailureCode;
3184
+ }>;
3185
+ interface ImageDoctorService {
3186
+ inspectLocal(config: ImagesServerConfig): Promise<ImageDoctorLocalSnapshot>;
3187
+ verifyLive(config: ImagesServerConfig, signal: AbortSignal): Promise<ImageDoctorLiveResult>;
3188
+ }
3189
+
3190
+ interface ImageTemporaryBudgetStatus {
3191
+ readonly activeScopes: number;
3192
+ readonly totalBytes: number;
3193
+ readonly tenantCount: number;
3194
+ }
3195
+ /** Process-local atomic budget shared by every scope in one pinned runtime generation. */
3196
+ declare class DaemonImageTemporaryBudget implements ImageTemporaryResourceBudget {
3197
+ #private;
3198
+ constructor(config: ImagesServerConfig['temporary']);
3199
+ acquireScope(tenantId: string): ImageTemporaryResourceBudgetLease;
3200
+ status(): ImageTemporaryBudgetStatus;
3201
+ }
3202
+ interface DaemonImageTemporaryResourceFactoryOptions {
3203
+ readonly paths: DaemonImagePathResolver;
3204
+ readonly config: ImagesServerConfig['temporary'];
3205
+ readonly activeScopes?: DaemonImageActiveScopeRegistry;
3206
+ }
3207
+ /** App-session registry shared by all runtime generations and recurring cleanup. */
3208
+ declare class DaemonImageActiveScopeRegistry {
3209
+ #private;
3210
+ constructor(paths: DaemonImagePathResolver);
3211
+ register(privateDirectory: string): () => void;
3212
+ isActive(privateDirectory: string): boolean;
3213
+ status(): {
3214
+ readonly activeDirectories: number;
3215
+ };
3216
+ }
3217
+ /** Binds the private root, fixed owner marker, tenant, and shared budget in one seam. */
3218
+ declare class DaemonImageTemporaryResourceFactory {
3219
+ #private;
3220
+ readonly budget: DaemonImageTemporaryBudget;
3221
+ constructor(options: DaemonImageTemporaryResourceFactoryOptions);
3222
+ readonly createResourceScope: (limits: ImageApiLimits, signal: AbortSignal, tenantId: string) => Promise<ImageRequestResourceScope>;
3223
+ }
3224
+
3225
+ interface ImageStartupReconcilerOptions {
3226
+ readonly catalog: ImageStorageMountCatalog;
3227
+ readonly temporaryPaths: DaemonImagePathResolver;
3228
+ readonly staleTemporaryAfterMs: number;
3229
+ readonly activeTemporaryScopes?: Pick<DaemonImageActiveScopeRegistry, 'isActive'>;
3230
+ readonly maxMountsPerPass?: number;
3231
+ readonly maxEntriesPerMount?: number;
3232
+ readonly maxTemporaryDirectoriesPerPass?: number;
3233
+ readonly now?: () => number;
3234
+ }
3235
+ interface ImageStartupReconciliationResult {
3236
+ readonly corruptManifestsQuarantined: number;
3237
+ readonly mountsVisited: number;
3238
+ readonly stateBindingsRemoved: number;
3239
+ readonly brokenBindingsRemoved: number;
3240
+ readonly referenceEntriesRemoved: number;
3241
+ readonly metadataRemoved: number;
3242
+ readonly metadataDegradedToProviderReference: number;
3243
+ readonly orphanFilesRemoved: number;
3244
+ readonly incompleteFilesRemoved: number;
3245
+ readonly transactionFilesRemoved: number;
3246
+ readonly temporaryDirectoriesRemoved: number;
3247
+ readonly foreignTemporaryDirectoriesSkipped: number;
3248
+ readonly activeTemporaryDirectoriesSkipped: number;
3249
+ readonly invalidDescendantsSkipped: number;
3250
+ readonly pendingReferenceDeletes: number;
3251
+ }
3252
+ /** One bounded, synchronous-filesystem startup pass; it never follows symlinks. */
3253
+ declare class ImageStartupReconciler {
3254
+ #private;
3255
+ constructor(options: ImageStartupReconcilerOptions);
3256
+ run(): Promise<ImageStartupReconciliationResult>;
3257
+ private hasLiveReference;
3258
+ private removeTransactionFiles;
3259
+ private removeStaleTemporaryDirectories;
3260
+ private removeTreeWithoutFollowingSymlinks;
3261
+ }
3262
+
3263
+ interface BoundedImageEvidenceCleanup {
3264
+ cleanup(now: number, limit: number): Promise<{
3265
+ readonly entriesRemoved: number;
3266
+ readonly bytesRemoved: number;
3267
+ }>;
3268
+ }
3269
+ interface ImageCleanupTimer {
3270
+ unref(): unknown;
3271
+ }
3272
+ interface ImageCleanupServiceOptions {
3273
+ readonly reconciler: ImageStartupReconciler;
3274
+ readonly catalog: ImageStorageMountCatalog;
3275
+ readonly intervalMs: number;
3276
+ readonly maxEvidenceEntriesPerPass?: number;
3277
+ readonly maxRetiredMountsPerPass?: number;
3278
+ readonly evidence?: BoundedImageEvidenceCleanup;
3279
+ readonly now?: () => number;
3280
+ readonly scheduleInterval?: (callback: () => void, intervalMs: number) => ImageCleanupTimer;
3281
+ readonly clearScheduledInterval?: (timer: ImageCleanupTimer) => void;
3282
+ }
3283
+ interface ImageCleanupPolicy {
3284
+ readonly reconciler: ImageStartupReconciler;
3285
+ readonly intervalMs: number;
3286
+ readonly evidence?: BoundedImageEvidenceCleanup;
3287
+ }
3288
+ interface PreparedImageCleanupPolicyChange {
3289
+ publish(): void;
3290
+ rollback(): void;
3291
+ dispose(): void;
3292
+ }
3293
+ interface ImageCleanupServicePassResult {
3294
+ readonly stateBindingsRemoved: number;
3295
+ readonly brokenBindingsRemoved: number;
3296
+ readonly referenceEntriesRemoved: number;
3297
+ readonly orphanFilesRemoved: number;
3298
+ readonly incompleteFilesRemoved: number;
3299
+ readonly transactionFilesRemoved: number;
3300
+ readonly temporaryDirectoriesRemoved: number;
3301
+ readonly evidenceEntriesRemoved: number;
3302
+ readonly evidenceBytesRemoved: number;
3303
+ readonly evidenceCleanupFailures: number;
3304
+ readonly retiredMountsRemoved: number;
3305
+ readonly referenceEntries: number;
3306
+ readonly referenceBytes: number;
3307
+ readonly referenceTombstones: number;
3308
+ readonly stateCalls: number;
3309
+ readonly stateResponses: number;
3310
+ readonly stateTombstones: number;
3311
+ readonly pendingReferenceDeletes: number;
3312
+ readonly finishedAt: number;
3313
+ }
3314
+ interface ImageCleanupServiceStatus extends ImageCleanupServicePassResult {
3315
+ readonly running: boolean;
3316
+ readonly passesCompleted: number;
3317
+ readonly passFailures: number;
3318
+ }
3319
+ /** Unref'ed recurring lifecycle owner with one non-overlapping bounded pass. */
3320
+ declare class ImageCleanupService {
3321
+ #private;
3322
+ constructor(options: ImageCleanupServiceOptions);
3323
+ start(): void;
3324
+ private schedule;
3325
+ /** Prepares a hot-reloadable cadence/reconciler/evidence snapshot. */
3326
+ preparePolicy(policy: ImageCleanupPolicy): PreparedImageCleanupPolicyChange;
3327
+ stop(): Promise<void>;
3328
+ reset(): Promise<void>;
3329
+ runOnce(): Promise<ImageCleanupServicePassResult>;
3330
+ status(): ImageCleanupServiceStatus;
3331
+ private performPass;
3332
+ }
3333
+
3334
+ interface TrustedImageApiRuntimeResolverOptions {
3335
+ readonly config: ImagesServerConfig;
3336
+ readonly referenceStore: ImageReferenceStore;
3337
+ /** Persistent private daemon key material. It is copied on construction. */
3338
+ readonly hmacKey: Uint8Array;
3339
+ /** Present only when the host has proved the complete remote-fetch policy. */
3340
+ readonly provenRemoteResolver?: RemoteImageAssetResolver;
3341
+ }
3342
+ interface TrustedImageApiRuntimeResolver {
3343
+ readonly resolve: ImageApiRuntimeResolver;
3344
+ dispose(): void;
3345
+ }
3346
+ /**
3347
+ * Resolves only daemon-authenticated route state. Named outbound keys retain
3348
+ * their stable key-id tenant; ephemeral Route Lease traffic falls back to the
3349
+ * authenticated lease id and remains isolated from other leases. Request
3350
+ * headers, including an inbound bearer, are deliberately outside the resolver's
3351
+ * identity inputs.
3352
+ */
3353
+ declare function createTrustedImageApiRuntimeResolver(options: TrustedImageApiRuntimeResolverOptions): TrustedImageApiRuntimeResolver;
3354
+
3355
+ interface DaemonImageExecutionSchedulerOptions {
3356
+ readonly config: ImagesServerConfig['queue'];
3357
+ /** Persistent private daemon key material; copied on construction. */
3358
+ readonly hmacKey: Uint8Array;
3359
+ }
3360
+ interface DaemonImageExecutionSchedulerStatus {
3361
+ readonly activeJobs: number;
3362
+ readonly waitingJobs: number;
3363
+ readonly activeAccounts: number;
3364
+ readonly waitingAccounts: number;
3365
+ readonly waitingTenants: number;
3366
+ readonly maxConcurrentJobsPerAccount: number;
3367
+ readonly maxQueuedJobs: number;
3368
+ readonly accepting: boolean;
3369
+ readonly shuttingDown: boolean;
3370
+ }
3371
+ /**
3372
+ * Snapshot-bound image admission with per-account active caps and fair tenant
3373
+ * rotation under one bounded global waiting population.
3374
+ */
3375
+ declare class DaemonImageExecutionScheduler implements ImageExecutionScheduler {
3376
+ #private;
3377
+ constructor(options: DaemonImageExecutionSchedulerOptions);
3378
+ deriveAccountKey(selectedAccountId: string): ImageExecutionAccountKey;
3379
+ acquire(request: ImageExecutionSchedulerRequest): Promise<ImageExecutionSchedulerGrant>;
3380
+ status(): DaemonImageExecutionSchedulerStatus;
3381
+ /** Stop new admissions while allowing already queued and active work to drain. */
3382
+ retire(): void;
3383
+ /** Reject waiters and cancel active grants. Safe to call repeatedly. */
3384
+ shutdown(): void;
3385
+ }
3386
+
3387
+ interface ImageRuntimeGenerationSharedStorage {
3388
+ /** Resolver associated with the catalog's already-active mount. */
3389
+ readonly paths: DaemonImagePathResolver;
3390
+ readonly referenceStore: MountedImageReferenceStore;
3391
+ readonly stateStore: MountedResponsesImageStateStore;
3392
+ }
3393
+ interface ImageRuntimeMetadataObservability {
3394
+ readonly telemetrySink?: ImageTelemetrySink;
3395
+ readonly audit?: (record: ImageApiAuditRecord) => void | Promise<void>;
3396
+ }
3397
+ /**
3398
+ * Explicit deterministic Tier-A seam. It replaces only the provider inside an
3399
+ * otherwise-production runtime generation and is never inferred from config.
3400
+ */
3401
+ interface SyntheticVerifiedImageProviderTestSeam {
3402
+ readonly label: 'synthetic-verified-image-provider-test-only';
3403
+ createProvider(context: Readonly<{
3404
+ generationId: string;
3405
+ scheduler: ImageExecutionScheduler;
3406
+ now: () => number;
3407
+ referenceStore: MountedImageReferenceStore;
3408
+ stateStore: MountedResponsesImageStateStore;
3409
+ }>): ImageProvider;
3410
+ }
3411
+ interface ImageRuntimeGenerationFactoryOptions {
3412
+ readonly generationId: string;
3413
+ readonly config: ImagesServerConfig;
3414
+ readonly subscriptionAccounts: Pick<SubscriptionAccountService, 'getStrategy'>;
3415
+ readonly storage: ImageRuntimeGenerationSharedStorage;
3416
+ readonly provenRemoteResolver?: RemoteImageAssetResolver;
3417
+ readonly observability?: ImageRuntimeMetadataObservability;
3418
+ readonly createRequestId?: () => string;
3419
+ readonly createCallId?: () => `ig_${string}`;
3420
+ readonly now?: () => number;
3421
+ /** Test seam; production loads the persistent daemon-local HMAC salt. */
3422
+ readonly privateHmacKey?: Uint8Array;
3423
+ /** App-session owner used to protect live directories across generations. */
3424
+ readonly activeTemporaryScopes?: DaemonImageActiveScopeRegistry;
3425
+ /** Immutable TTL view scoped to this runtime generation. */
3426
+ readonly evidenceSource?: FileCodexImageCapabilityEvidenceSource;
3427
+ /** App-session catalog lease retained until this generation is disposed. */
3428
+ readonly releaseStorageBackend?: () => void;
3429
+ /** Test-only Tier-A provider; production leaves this absent. */
3430
+ readonly testOnlySyntheticVerifiedProvider?: SyntheticVerifiedImageProviderTestSeam;
3431
+ }
3432
+ interface ProductionImageRuntimeComponents {
3433
+ readonly providerRegistry: ImageProviderRegistry;
3434
+ readonly orchestrator: ImageOrchestrator;
3435
+ readonly scheduler: DaemonImageExecutionScheduler;
3436
+ readonly evidenceSource: FileCodexImageCapabilityEvidenceSource;
3437
+ readonly temporaryResources: DaemonImageTemporaryResourceFactory;
3438
+ readonly runtimeResolver: TrustedImageApiRuntimeResolver;
3439
+ readonly referenceStore: MountedImageReferenceStore;
3440
+ readonly stateStore: MountedResponsesImageStateStore;
3441
+ }
3442
+ type ProductionImageRuntimeGeneration = PreparedImageRuntimeGeneration & {
3443
+ readonly components?: ProductionImageRuntimeComponents;
3444
+ };
3445
+ /**
3446
+ * Builds a complete generation without changing the storage catalog's active
3447
+ * mount. Catalog activation must be composed by a failure-atomic transaction.
3448
+ */
3449
+ declare function createImageRuntimeGeneration(options: ImageRuntimeGenerationFactoryOptions): ProductionImageRuntimeGeneration;
3450
+
3451
+ /**
3452
+ * JsonOutboundKeyDb — the daemon's file-backed `OutboundKeyDb` port impl
3453
+ * (design D3).
3454
+ *
3455
+ * Durable storage for named outbound API keys, backed by a json file (a sibling
3456
+ * of `config.json`, e.g. `keys.json`) holding an `OutboundKeyDbRow[]`. This port
3457
+ * provides ONLY storage — it never generates secrets nor hashes. Core's
3458
+ * `createNamedKey(db, name)` calls `outboundApiKeysCreate` with the sha256
3459
+ * `keyHash` + display `keyPrefix` and returns the one-time plaintext; the hot
3460
+ * auth path uses core's `hashKey(presented)` + `outboundApiKeysGetByHash`.
3461
+ *
3462
+ * OPTIONAL reversible storage: when constructed with a `SecretBox`, each created
3463
+ * key ALSO persists its plaintext as a `keySecret` `enc:` envelope, powering the
3464
+ * operator "view key" affordance (`outboundApiKeysReveal`). The hash remains the
3465
+ * auth index; without a box the store is hash-only (byte-identical to legacy).
3466
+ *
3467
+ * @module @omnicross/daemon/ports/JsonOutboundKeyDb
3468
+ */
3469
+
3470
+ type AtomicFileReplace = (targetPath: string, contents: string) => void;
2236
3471
  declare class JsonOutboundKeyDb implements OutboundKeyDb {
2237
3472
  private readonly keysPath;
2238
3473
  private readonly secretBox?;
3474
+ private readonly atomicReplace;
2239
3475
  /**
2240
3476
  * @param secretBox OPTIONAL reversible-secret codec. When present, a created
2241
3477
  * key's plaintext is persisted as a `keySecret` `enc:` envelope (enabling the
@@ -2244,7 +3480,7 @@ declare class JsonOutboundKeyDb implements OutboundKeyDb {
2244
3480
  * always returns `null`. Existing 1-arg call sites (tests, lightweight
2245
3481
  * embedders) keep working.
2246
3482
  */
2247
- constructor(keysPath: string, secretBox?: SecretBox | undefined);
3483
+ constructor(keysPath: string, secretBox?: SecretBox | undefined, atomicReplace?: AtomicFileReplace);
2248
3484
  outboundApiKeysList(): Promise<OutboundKeyDbRow[]>;
2249
3485
  outboundApiKeysGetByHash(hash: string): Promise<OutboundKeyDbRow | null>;
2250
3486
  outboundApiKeysCreate(input: {
@@ -2254,7 +3490,7 @@ declare class JsonOutboundKeyDb implements OutboundKeyDb {
2254
3490
  keyPrefix: string;
2255
3491
  createdAt?: number;
2256
3492
  kind?: 'client' | 'integration';
2257
- allowedEndpoints?: _omnicross_core.OutboundEndpoint[];
3493
+ allowedEndpoints?: OutboundPermission[];
2258
3494
  loopbackOnly?: boolean;
2259
3495
  plaintext?: string;
2260
3496
  }): Promise<OutboundKeyDbRow>;
@@ -2263,6 +3499,7 @@ declare class JsonOutboundKeyDb implements OutboundKeyDb {
2263
3499
  outboundApiKeysRevoke(id: string): Promise<boolean>;
2264
3500
  outboundApiKeysTouchLastUsed(id: string): Promise<boolean>;
2265
3501
  outboundApiKeysSetEnabled(id: string, enabled: boolean): Promise<boolean>;
3502
+ outboundApiKeysSetPermissions(id: string, permissions: OutboundPermission[]): Promise<boolean>;
2266
3503
  outboundApiKeysSetMaxConcurrency(id: string, maxConcurrency: number | null): Promise<boolean>;
2267
3504
  outboundApiKeysSetPolicy(id: string, policy: OutboundKeyPolicy): Promise<boolean>;
2268
3505
  outboundApiKeysMarkActivated(id: string, activatedAt: number): Promise<boolean>;
@@ -2424,6 +3661,52 @@ declare class AuditPruneSweeper {
2424
3661
  private archiveDay;
2425
3662
  }
2426
3663
 
3664
+ /**
3665
+ * usageMigrate — the ONE-SHOT fold of the legacy flat `usage-events.jsonl` into
3666
+ * per-LOCAL-day shards plus their immutable rollups.
3667
+ *
3668
+ * Runs at bootstrap, BEFORE any listener is bound, so no request can be served
3669
+ * against a half-migrated store. It streams: the legacy file is never held in
3670
+ * memory as one string (that is the exact shape that put the old store on course
3671
+ * for V8's ~512 MB string ceiling), and each day's rollup is accumulated during
3672
+ * the same pass that writes its shard, so the whole migration is a single read.
3673
+ *
3674
+ * Writes are BATCHED per day (see {@link FLUSH_BYTES}). Awaiting a write per row
3675
+ * turned 199k rows into ~17 s of boot on a real 157 MB store, which the desktop
3676
+ * shell — waiting on the admin port — is right to find suspicious; batching cuts
3677
+ * it to a few seconds without changing what lands on disk.
3678
+ *
3679
+ * CRASH SAFETY. Everything is built inside `usage/.migrating/` and only moved
3680
+ * into place once the row count reconciles. A crash before the commit leaves the
3681
+ * legacy file untouched and the scratch directory to be wiped and rebuilt on the
3682
+ * next attempt. A crash DURING the commit leaves some shards in place and the
3683
+ * legacy file still present; the next attempt refuses to run (a target shard
3684
+ * already exists) and says so, rather than overwriting or double-counting rows.
3685
+ *
3686
+ * RECONCILIATION is the point of the exercise: `linesRead` must equal
3687
+ * `rowsWritten + skipped`, where `skipped` is lines the shared row guard rejects.
3688
+ * If it does not, nothing is committed and the legacy file stays exactly where it
3689
+ * was. Silently losing usage rows would corrupt lifetime key spend, which is the
3690
+ * one number in this store that can never be reconstructed.
3691
+ *
3692
+ * @module @omnicross/daemon/usage/usageMigrate
3693
+ */
3694
+
3695
+ interface UsageMigrationResult {
3696
+ /** True only when the legacy file was folded in and removed. */
3697
+ migrated: boolean;
3698
+ /** Non-empty lines read from the legacy file. */
3699
+ linesRead: number;
3700
+ /** Rows that passed the guard and were written to a shard. */
3701
+ rowsWritten: number;
3702
+ /** Lines the guard rejected (torn tail, hand-edited, foreign writer). */
3703
+ skipped: number;
3704
+ /** Distinct LOCAL days produced. */
3705
+ days: number;
3706
+ /** Present when the migration declined or aborted; nothing was committed. */
3707
+ reason?: string;
3708
+ }
3709
+
2427
3710
  /**
2428
3711
  * AuditWriter — the daemon's file-backed audit sink (request-audit-log design
2429
3712
  * D4/D5, re-laid-out by audit-store-sharding design D2). Registered as
@@ -2824,6 +4107,16 @@ interface DaemonPaths {
2824
4107
  * never invoke a real package manager. Absent → the real `exec`-based runner.
2825
4108
  */
2826
4109
  cliCommandRunner?: CommandRunner;
4110
+ /** TEST/COMPOSITION SEAM: prepared Images runtime generation for config transactions. */
4111
+ imageRuntimeConfig?: AdminApiDeps['imageRuntimeConfig'];
4112
+ /** TEST/COMPOSITION SEAM: metadata-only Images status reader. */
4113
+ imageRuntimeStatus?: AdminApiDeps['imageRuntimeStatus'];
4114
+ /** TEST/COMPOSITION SEAM: metadata-only successful Images config audit sink. */
4115
+ imageConfigAudit?: AdminApiDeps['imageConfigAudit'];
4116
+ /** TEST ONLY: deterministic Tier-A provider inside the production Images composition. */
4117
+ testOnlySyntheticVerifiedImageProvider?: SyntheticVerifiedImageProviderTestSeam;
4118
+ /** TEST SEAM: inject an atomic settings replacement fault. */
4119
+ settingsAtomicReplace?: (targetPath: string, contents: Uint8Array) => void;
2827
4120
  }
2828
4121
  /** The constructed daemon handles the CLI commands operate on. */
2829
4122
  interface Daemon {
@@ -2832,6 +4125,25 @@ interface Daemon {
2832
4125
  readonly llmConfig: ConfigFileProviderConfigSource;
2833
4126
  readonly keyDb: JsonOutboundKeyDb;
2834
4127
  readonly settingsStore: JsonApiServerSettingsStore;
4128
+ /** App-session extension-operation registry shared with the resident proxy. */
4129
+ readonly openAIOperationRegistry: OpenAIOperationRegistry;
4130
+ /**
4131
+ * The ONE search runtime for this daemon (plan 阶段5 §6.3). The same object
4132
+ * the Codex route, both managed frontends and the search doctor hold.
4133
+ */
4134
+ readonly searchRuntime: SearchRuntime;
4135
+ /** Per-frontend search modes as loaded at bootstrap. */
4136
+ readonly searchFrontendModes: SearchFrontendModes;
4137
+ /** Stable Images forwarders and generation lifecycle owner for this app session. */
4138
+ readonly imageRuntimeManager: ImageRuntimeManager;
4139
+ /** Bounded process-local metadata aggregation shared by every runtime generation. */
4140
+ readonly imageObservability: ImageObservability;
4141
+ /** Startup reconciliation plus recurring bounded cleanup for Images state. */
4142
+ readonly imageCleanupService: ImageCleanupService;
4143
+ /** Local-only diagnostics plus the explicit consuming Images verifier. */
4144
+ readonly imageDoctor: ImageDoctorService;
4145
+ /** Generation-pinned hosted-image lease factory used by Native Responses. */
4146
+ readonly hostedImageContributionFactory: HostedImageContributionFactory;
2835
4147
  readonly providerProxy: ProviderProxy;
2836
4148
  readonly routeLeaseManager: RouteLeaseManager;
2837
4149
  readonly outboundApiServer: OutboundApiServer;
@@ -2909,6 +4221,17 @@ interface Daemon {
2909
4221
  * in cleanup.
2910
4222
  */
2911
4223
  readonly auditPruneSweeper: AuditPruneSweeper;
4224
+ /**
4225
+ * Retention for RAW usage rows (rollups are kept forever). Armed by `start`;
4226
+ * `launch` leaves it off — a short-lived boot has no business pruning.
4227
+ */
4228
+ readonly usagePruneSweeper: UsagePruneSweeper;
4229
+ /**
4230
+ * Fold a legacy flat `usage-events.jsonl` into day shards. MUST be awaited
4231
+ * before any listener binds: it moves files the query path reads, and a
4232
+ * request served mid-migration would see a partial store.
4233
+ */
4234
+ readonly migrateUsageStore: () => Promise<UsageMigrationResult>;
2912
4235
  /**
2913
4236
  * Durable-first billing publisher (billing-event-stream) — appends each event
2914
4237
  * to `billing/billing-YYYY-MM-DD.jsonl` FIRST, then best-effort POSTs it.
@@ -2925,11 +4248,6 @@ interface Daemon {
2925
4248
  */
2926
4249
  readonly billingRetrySweeper: BillingRetrySweeper;
2927
4250
  }
2928
- /**
2929
- * Construct the standalone daemon from a loaded config + on-disk paths. Does NOT
2930
- * start the listeners — the `start` command awaits `providerProxy.start()` then
2931
- * `outboundApiServer.applyConfig(...)`.
2932
- */
2933
4251
  declare function buildDaemon(config: DaemonConfig, paths: DaemonPaths): Daemon;
2934
4252
  /** Reset the core singletons (tests / teardown only). Re-exported for the suite.
2935
4253
  *
@@ -3015,11 +4333,24 @@ declare function buildHealthReport(deps: HealthReportDeps): HealthReport;
3015
4333
  * - SINK: always the console; PLUS an optional append-only file stream when
3016
4334
  * `file` is set (lazy-open; a write/open error is swallowed → the daemon never
3017
4335
  * crashes on a logging failure, it just falls back to the console).
4336
+ * - ROTATION: the file sink is size-capped (`maxFileBytes`, default 8 MB) and
4337
+ * keeps `maxFiles` generations (default 5) as `<file>.1` … `<file>.N`. An
4338
+ * UNBOUNDED append-only log is how a long-lived daemon quietly fills a disk,
4339
+ * so the cap is on by default rather than opt-in. Lines emitted mid-rotation
4340
+ * are queued (bounded) and flushed into the fresh generation, never dropped
4341
+ * silently unless the queue itself overflows.
3018
4342
  *
3019
4343
  * ZERO-REGRESSION DEFAULT: `new ConfigurableLogger()` (no config) = console +
3020
4344
  * all levels + text = behaviorally byte-identical to the legacy `ConsoleLogger`
3021
4345
  * (same `console` method per level, same `(message[, meta])` / error arg shape).
3022
4346
  *
4347
+ * NOTE — the daemon no longer CONSTRUCTS it that way. `bootstrap.ts` defaults the
4348
+ * file sink on (`<configDir>/logs/daemon.log`, level `info`, format `json`)
4349
+ * whenever the config omits it: the desktop app discards the daemon's stdout, so
4350
+ * a console-only logger meant a crash left no evidence at all. The unconfigured
4351
+ * CONSTRUCTOR default above is still console-only — only the daemon's wiring
4352
+ * changed.
4353
+ *
3023
4354
  * CAUTION (per the #3 host:port-only-logging precedent): the JSON serializer
3024
4355
  * reduces an `Error` to `{ message, stack }` and spreads a plain `meta` object,
3025
4356
  * but it is NOT a secret redactor — call sites remain responsible for not passing
@@ -3032,8 +4363,16 @@ declare class ConfigurableLogger implements Logger {
3032
4363
  private readonly threshold;
3033
4364
  private readonly format;
3034
4365
  private readonly filePath;
4366
+ private readonly maxFileBytes;
4367
+ private readonly maxFiles;
3035
4368
  private fileStream;
3036
4369
  private fileDisabled;
4370
+ /** Bytes in the CURRENT generation — seeded from the file on open. */
4371
+ private fileBytes;
4372
+ /** True from the moment a rotation starts until the fresh stream is live. */
4373
+ private rotating;
4374
+ /** Lines emitted while `rotating`; flushed into the new generation. */
4375
+ private rotateQueue;
3037
4376
  constructor(cfg?: LoggingConfig);
3038
4377
  info(message: string, meta?: Record<string, unknown> | Error | object): void;
3039
4378
  warn(message: string, meta?: Record<string, unknown> | Error | object): void;
@@ -3053,7 +4392,22 @@ declare class ConfigurableLogger implements Logger {
3053
4392
  private writeConsole;
3054
4393
  /** Append one line to the file sink; a failure disables the sink (swallowed). */
3055
4394
  private writeFile;
3056
- /** Lazily open the append-only file stream; disable the sink on any error. */
4395
+ /** Write ONE prepared line to the live generation, rotating once it is full. */
4396
+ private emitLine;
4397
+ /**
4398
+ * Close the full generation, shift `<file>.N-1` → `<file>.N` (oldest dropped),
4399
+ * then reopen. Renames run in the `end()` callback so nothing still buffered in
4400
+ * the stream lands in the wrong generation. Any failure disables the sink
4401
+ * rather than throwing — the console sink is unaffected either way.
4402
+ */
4403
+ private rotate;
4404
+ /** `<file>.N` unlinked, `<file>.k` → `<file>.k+1`, `<file>` → `<file>.1`. */
4405
+ private shiftGenerations;
4406
+ /**
4407
+ * Lazily open the append-only file stream; disable the sink on any error. The
4408
+ * byte counter is seeded from the file already on disk so a restart cannot
4409
+ * reset an almost-full generation back to zero and blow past the cap.
4410
+ */
3057
4411
  private getFileStream;
3058
4412
  private consoleFn;
3059
4413
  /** `{ ts, level, msg, ...meta }` (+ `error` when present) as a single line. */
@@ -3143,4 +4497,4 @@ declare function mapCcrToOmnicross(ccr: CcrConfig): {
3143
4497
  notes: string[];
3144
4498
  };
3145
4499
 
3146
- 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, JsonApiServerSettingsStore, JsonOutboundKeyDb, JsonSubscriptionCredentialStore, type ResolvedAdminConfig, buildDaemon, buildHealthReport, handleAdminApi, inferApiFormat, loadConfig, mapCcrToOmnicross, parseCcrConfig, resetDaemonSingletonsForTests, resolveAdminConfig, saveConfig, validateConfig };
4500
+ 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 };