@omnicross/daemon 0.1.9 → 0.2.0

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.
Files changed (7) hide show
  1. package/dist/cli.cjs +11392 -3527
  2. package/dist/cli.js +11730 -3705
  3. package/dist/index.cjs +8551 -1104
  4. package/dist/index.d.cts +1452 -58
  5. package/dist/index.d.ts +1452 -58
  6. package/dist/index.js +8525 -926
  7. package/package.json +63 -63
package/dist/index.d.cts CHANGED
@@ -1,10 +1,9 @@
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';
3
2
  import { ApiKeyPoolService } from '@omnicross/core/completion/ApiKeyPoolService';
4
- import { AllowanceSchedulingConfig, AccountProbeConfig, OutboundKeyDb as OutboundKeyDb$1, VoucherDb, KeySpendReader, OutboundApiServer } from '@omnicross/core/outbound-api';
3
+ import { AllowanceSchedulingConfig, AccountProbeConfig, OutboundKeyDb as OutboundKeyDb$1, VoucherDb, KeySpendReader, OutboundApiServer, ImagesServerConfig } from '@omnicross/core/outbound-api';
5
4
  import { RouteLeaseManager, ProviderProxy } from '@omnicross/core/provider-proxy';
6
5
  import { UsageRecorder, PricingEngine } from '@omnicross/core/usage';
7
- import { SubscriptionCredentialStore, FetchLike, SubscriptionProviderRegistry, SubscriptionAccountService } from '@omnicross/subscriptions';
6
+ import { SubscriptionCredentialStore, FetchLike, CodexImageCapabilityEvidenceSource, CodexImageCapabilityEvidenceRequest, CodexImageCapabilityEvidence, CodexImageCapabilityObservation, ImageExecutionScheduler, ImageExecutionAccountKey, ImageExecutionSchedulerRequest, ImageExecutionSchedulerGrant, SubscriptionAccountService, SubscriptionProviderRegistry } from '@omnicross/subscriptions';
8
7
  import { AccountAllowanceSnapshot } from '@omnicross/contracts/account-allowance-types';
9
8
  import { ClaudeTokenConfig, CodexTokenConfig, GeminiTokenConfig, AccountTokensConfig, ProxyConfig, SubscriptionAccountSanitized, AccountClientIdentity, SubscriptionAccountEntry } from '@omnicross/contracts/account-tokens-types';
10
9
  import * as _omnicross_contracts_subscription_types from '@omnicross/contracts/subscription-types';
@@ -15,12 +14,17 @@ import { SubscriptionIdentityStore } from '@omnicross/core/provider-proxy/identi
15
14
  import { LoggingConfig, HealthReport } from '@omnicross/contracts/health-logging-types';
16
15
  import { SubscriptionAccountHealth } from '@omnicross/core/pipeline/SubscriptionAccountHealth';
17
16
  import { fetchUpstream } from '@omnicross/core/pipeline/upstreamFetch';
17
+ import { UsageTotals, ModelUsageRow, ApiKeyUsageRow } from '@omnicross/contracts/usage-stats-types';
18
18
  import { ThinkLevel } from '@omnicross/contracts/completion-types';
19
- import { AuditRecord, AuditStats, AuditConfig } from '@omnicross/contracts/audit-types';
19
+ import { AuditRecord, AuditStats, AuditBodyResult, AuditConfig } from '@omnicross/contracts/audit-types';
20
20
  import { BillingDeliveryStatus, BillingConfig, BillingEvent } from '@omnicross/contracts/billing-types';
21
21
  import http from 'node:http';
22
+ import * as _omnicross_contracts_image_generation_types from '@omnicross/contracts/image-generation-types';
23
+ import { ImageCapabilityUnavailableReason, ImageCapabilities, ImageGenerationErrorCode, ImageReferenceMetadata, ImageReferenceId } from '@omnicross/contracts/image-generation-types';
24
+ 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
25
  import { LLMProvider, AgentDefaultModels, GlobalModelParameters } from '@omnicross/contracts/llm-config';
23
26
  import { PricingEntry, PricingEntryInput, PricingResolution, PricingSourceRefreshResult } from '@omnicross/contracts/pricing-types';
27
+ import { ResponsesImageStateStore, ResponsesImageStateCommitInput, ResponsesImageCallBinding, ResponsesImageCallId, ResponsesImageCallResolution, ResponsesImageResponseResolution } from '@omnicross/core/image-generation/responses';
24
28
  import { WebhookConfig, WebhookEvent } from '@omnicross/contracts/webhook-types';
25
29
 
26
30
  /**
@@ -86,6 +90,193 @@ declare class SecretBox {
86
90
  encryptMaybe(value: string): string;
87
91
  }
88
92
 
93
+ /**
94
+ * usageRollup — the immutable per-day aggregate that lets a range query skip a
95
+ * day's rows entirely.
96
+ *
97
+ * A rollup is computed ONCE, when a day is closed (strictly before today's local
98
+ * date), and is then never rewritten. It is deliberately kept FOREVER, outliving
99
+ * the raw shard it was built from, for one load-bearing reason:
100
+ * `getSpendByKey().totalUsd` is a LIFETIME number that seeds the outbound key
101
+ * spend policy (`core/outbound-api/keySpendTracker`). Pruning raw rows without
102
+ * keeping a per-key aggregate behind would silently reset every key's lifetime
103
+ * spend and quietly re-open budgets the operator had already spent.
104
+ *
105
+ * WHAT IS EXACT AND WHAT IS NOT
106
+ *
107
+ * Every count, token sum and cost in a rollup is exact — it is a plain sum of
108
+ * the same rows a raw scan would have visited. The ONE approximation is
109
+ * `medianCacheHitRate`: a median cannot be composed from per-day medians, so
110
+ * each day stores a 1000-bin histogram of its per-event hit rates instead. The
111
+ * median recovered from bins is off by at most half a bin — 0.0005, i.e. well
112
+ * under a twentieth of a percentage point. Storing the raw rates instead would
113
+ * make a "permanent" sidecar grow with traffic, which is the property this whole
114
+ * layout exists to avoid.
115
+ *
116
+ * STALENESS: a rollup records the byte size of the shard it was built from.
117
+ * Because a shard is append-only, a size change means rows were added after the
118
+ * rollup was computed (only reachable by inserting into a past day — the tests
119
+ * do it, production does not) and the rollup is recomputed. A shard that is GONE
120
+ * is the pruned case, where the rollup is the sole surviving authority.
121
+ *
122
+ * @module @omnicross/daemon/usage/usageRollup
123
+ */
124
+
125
+ /** Everything in `UsageTotals` except the one field that cannot be summed. */
126
+ type SummableTotals = Omit<UsageTotals, 'medianCacheHitRate'>;
127
+ /** Per-model group inside a rollup. `unpriced` is deliberately absent — it is
128
+ * derived at query time from the injected pricing lookup, so a pricing change
129
+ * never requires rewriting an immutable rollup. */
130
+ type RollupModelRow = Omit<ModelUsageRow, 'unpriced'>;
131
+ /** Per-key group inside a rollup. `label` is absent for the same reason: it is
132
+ * resolved by the admin handler against the live key registry. */
133
+ type RollupApiKeyRow = Omit<ApiKeyUsageRow, 'label'>;
134
+ /** One hour of a day. Only hours that saw traffic are stored. */
135
+ interface RollupHourRow {
136
+ /** LOCAL hour of day, 0–23. */
137
+ hour: number;
138
+ requests: number;
139
+ inputTokens: number;
140
+ outputTokens: number;
141
+ cacheReadTokens: number;
142
+ cacheCreationTokens: number;
143
+ costUsd: number;
144
+ }
145
+ /** The on-disk `usage-YYYY-MM-DD.rollup.json` document. */
146
+ interface UsageDayRollup {
147
+ version: 1;
148
+ /** `YYYY-MM-DD`, LOCAL. Must match the file name. */
149
+ date: string;
150
+ /** Byte size of the shard this was built from; the staleness guard. */
151
+ sourceBytes: number;
152
+ totals: SummableTotals;
153
+ /** Sparse `binIndex -> count` over per-event cache-hit rates. */
154
+ hitRateBins: Record<string, number>;
155
+ byModel: RollupModelRow[];
156
+ byApiKey: RollupApiKeyRow[];
157
+ byHour: RollupHourRow[];
158
+ /** Distinct non-null session ids seen that day (drilldown index). */
159
+ sessionIds: string[];
160
+ /** True when `sessionIds` hit {@link SESSION_INDEX_LIMIT} and is incomplete. */
161
+ sessionIdsTruncated: boolean;
162
+ }
163
+
164
+ /**
165
+ * usageRollupStore — read-or-build the per-day rollup sidecars.
166
+ *
167
+ * A rollup is built LAZILY, the first time a query needs a closed day, and then
168
+ * reused forever. Building it is the only time that day's rows are ever parsed
169
+ * again; every subsequent query for any range containing that day reads a few KB
170
+ * of JSON instead of megabytes of JSONL.
171
+ *
172
+ * STALENESS is checked against the shard's byte size, not its mtime. Shards are
173
+ * append-only, so a size that no longer matches what the rollup was built from
174
+ * means rows were added afterwards — only reachable by inserting an event with a
175
+ * past `ts`, which the store API permits and the tests exercise. A shard that is
176
+ * absent entirely is the PRUNED case: the rollup is then the sole surviving
177
+ * record of that day and is trusted unconditionally.
178
+ *
179
+ * Writes are atomic (temp file + rename) so a crash mid-write cannot leave a
180
+ * half-JSON sidecar that later reads as a corrupt day.
181
+ *
182
+ * @module @omnicross/daemon/usage/usageRollupStore
183
+ */
184
+
185
+ declare class UsageRollupStore {
186
+ private readonly usageDir;
187
+ private readonly maxCached;
188
+ private readonly cache;
189
+ /** In-flight builds, so a burst of concurrent queries builds a day ONCE. */
190
+ private readonly inFlight;
191
+ constructor(usageDir: string, maxCached?: number);
192
+ /**
193
+ * The rollup for a CLOSED day, building it from the shard when absent or
194
+ * stale. `null` means the day has neither a usable rollup nor a shard — i.e.
195
+ * nothing was ever recorded for it.
196
+ *
197
+ * Callers must not ask for TODAY: today's rows are still arriving, so a rollup
198
+ * of them would be wrong the moment it was written.
199
+ */
200
+ get(dayKey: string, hasShard?: boolean): Promise<UsageDayRollup | null>;
201
+ /** Forget a day — used when an insert lands in an already-rolled-up day. */
202
+ invalidate(dayKey?: string): void;
203
+ /**
204
+ * Build (or rebuild) a day's rollup from its shard and persist it. Exposed for
205
+ * the prune sweeper, which must guarantee a rollup exists BEFORE it deletes
206
+ * the rows that rollup is derived from.
207
+ */
208
+ ensure(dayKey: string): Promise<UsageDayRollup | null>;
209
+ private resolve;
210
+ private readSidecar;
211
+ private writeSidecar;
212
+ private remember;
213
+ }
214
+
215
+ /**
216
+ * UsagePruneSweeper — retention for the sharded usage store.
217
+ *
218
+ * Prunes RAW day shards past `retentionDays` and NEVER touches a rollup. That
219
+ * asymmetry is the whole design: aggregates (totals, per-model, per-key, hourly
220
+ * trend) stay complete for the lifetime of the install, while the per-row detail
221
+ * that costs 5 MB a day is bounded. In particular `getSpendByKey().totalUsd` —
222
+ * the LIFETIME figure seeding the outbound key spend policy — keeps summing
223
+ * correctly across pruned days, which a naive "delete old rows" would silently
224
+ * zero, reopening budgets the operator had already spent.
225
+ *
226
+ * ORDERING IS LOAD-BEARING: a shard is only ever deleted AFTER its rollup exists
227
+ * on disk. If the rollup cannot be built, the shard is kept and the day is
228
+ * retried on the next tick. Deleting rows whose aggregate was never computed
229
+ * would destroy that day permanently.
230
+ *
231
+ * Shaped like `audit/AuditPruneSweeper`: `start()` arms an `unref()`ed interval
232
+ * plus one immediate pass, `dispose()` clears it, a re-entrancy guard prevents
233
+ * overlap, and nothing throws — a failed sweep is logged and retried, never
234
+ * fatal to the daemon.
235
+ *
236
+ * @module @omnicross/daemon/usage/UsagePruneSweeper
237
+ */
238
+
239
+ /** The `usage` config segment. */
240
+ interface UsageRetentionConfig {
241
+ /** Days of RAW rows to keep. `0` or absent ⇒ keep everything. */
242
+ retentionDays?: number;
243
+ }
244
+ declare class UsagePruneSweeper {
245
+ private readonly usageDir;
246
+ private readonly rollups;
247
+ private readonly logger;
248
+ private config;
249
+ private readonly intervalMs;
250
+ /** Injectable clock (ms) for deterministic tests. */
251
+ private readonly now;
252
+ private timer;
253
+ private sweeping;
254
+ constructor(usageDir: string, rollups: UsageRollupStore, logger: Logger, config?: UsageRetentionConfig, intervalMs?: number,
255
+ /** Injectable clock (ms) for deterministic tests. */
256
+ now?: () => number);
257
+ /** Whether raw rows are pruned at all. */
258
+ get enabled(): boolean;
259
+ /** Re-apply config to the live instance (boot + admin PUT hot-reload). */
260
+ configure(config: UsageRetentionConfig): void;
261
+ /**
262
+ * Arm the interval AND run one pass immediately (boot cleanup). No-op when
263
+ * retention is off (keep-everything is the zero-regression default).
264
+ * Idempotent.
265
+ */
266
+ start(): void;
267
+ /** Clear the interval (daemon shutdown / test teardown). Idempotent. */
268
+ dispose(): void;
269
+ /**
270
+ * One prune pass: for every day strictly OLDER than the retention cutoff that
271
+ * still has a shard, make sure its rollup exists and only then delete the
272
+ * shard. Exposed for tests; never throws. Returns the number of shards
273
+ * removed.
274
+ */
275
+ sweep(): Promise<number>;
276
+ /** The LOCAL-midnight epoch ms of the current day. */
277
+ private todayMidnight;
278
+ }
279
+
89
280
  /**
90
281
  * config.ts — the daemon's `config.json` schema + load/save (design D9).
91
282
  *
@@ -411,6 +602,12 @@ interface DaemonConfig {
411
602
  * so it is never walked by `decryptConfigSecrets`/`encryptConfigSecrets`.
412
603
  */
413
604
  logging?: LoggingConfig;
605
+ /**
606
+ * Optional usage-retention config. Absent ⇒ the default 90-day window on RAW
607
+ * usage rows. Per-day ROLLUPS are never pruned regardless — they carry the
608
+ * lifetime per-key spend the outbound key policy seeds from.
609
+ */
610
+ usage?: UsageRetentionConfig;
414
611
  }
415
612
  /** Apply defaults to a (possibly absent) admin block: enabled, port 8766,
416
613
  * loopback, no token. NOTE: an EXPLICIT `port: 0` is honored as "bind an
@@ -1421,11 +1618,53 @@ declare class AccountHealthProbeScheduler implements AccountProbeHistoryReader {
1421
1618
  }
1422
1619
 
1423
1620
  /**
1424
- * auditReaderread + filter the date-rotated audit store (request-audit-log,
1425
- * design D4/D6). Backs the AUTHED admin query only (the records carry IP/UA +
1426
- * possibly bodies). Reads the relevant `audit-*.jsonl` files, parses defensively
1427
- * (a torn final line never poisons a query), filters by key id + time window, and
1428
- * returns NEWEST-FIRST up to a bounded limit.
1621
+ * auditBodyReaderreconstruct captured bodies out of the per-session shards
1622
+ * (audit-store-sharding, design D4).
1623
+ *
1624
+ * Bodies no longer sit inline on the metadata line, so reading one is an explicit
1625
+ * second step: locate the session shard, replay its delta chain, hand back the
1626
+ * original text. Backs the AUTHED admin body query and the `omnicross audit` CLI
1627
+ * — never an unauthenticated surface (a body can hold prompts and PII).
1628
+ *
1629
+ * Shards are read whole. That is bounded on purpose: a shard holds ONE session's
1630
+ * deltas, which is exactly the thing the delta encoding keeps small.
1631
+ *
1632
+ * Both storage forms are handled transparently: today's plain `.jsonl` (kept
1633
+ * greppable on disk) and an archived `.jsonl.gz` from a rolled-over day.
1634
+ *
1635
+ * @module @omnicross/daemon/audit/auditBodyReader
1636
+ */
1637
+
1638
+ /** Locate one record's bodies. `ts` narrows the search to a single day directory. */
1639
+ interface AuditBodyQuery {
1640
+ /** The audit record id whose bodies to reconstruct. */
1641
+ id: string;
1642
+ /** The record's session key (its shard). */
1643
+ sessionKey: string;
1644
+ /** The record's timestamp, if known — skips scanning every retained day. */
1645
+ ts?: number;
1646
+ }
1647
+
1648
+ /**
1649
+ * auditReader — read + filter the audit store (request-audit-log design D4/D6,
1650
+ * re-laid-out by audit-store-sharding design D5). Backs the AUTHED admin query
1651
+ * only (records carry IP/UA). Returns NEWEST-FIRST up to a bounded limit.
1652
+ *
1653
+ * Reads BOTH on-disk layouts: the current `audit-YYYY-MM-DD/meta.jsonl` and the
1654
+ * legacy flat `audit-YYYY-MM-DD.jsonl` left behind by an older daemon (still
1655
+ * queryable until TTL prunes it — there is no migration step).
1656
+ *
1657
+ * BOUNDED BY CONSTRUCTION. Days are visited newest-first and each file is walked
1658
+ * BACKWARDS from its tail, so a query touches only as much of the store as it
1659
+ * needs. Two stops apply:
1660
+ * - cross-day: once `limit` rows are in hand, no older day is opened at all.
1661
+ * Exact, because day files never overlap in date.
1662
+ * - within-day: at most `limit + OVERSCAN` rows are taken from one file. The
1663
+ * overscan absorbs the slight append-order skew from a slow request landing
1664
+ * after a fast one that started later.
1665
+ *
1666
+ * A returned record NEVER carries a body — those live in the per-session shards
1667
+ * and are fetched one at a time. `hasBody` says whether one exists.
1429
1668
  *
1430
1669
  * @module @omnicross/daemon/audit/auditReader
1431
1670
  */
@@ -1434,6 +1673,8 @@ declare class AccountHealthProbeScheduler implements AccountProbeHistoryReader {
1434
1673
  interface AuditQuery {
1435
1674
  /** Restrict to one outbound key id. */
1436
1675
  keyId?: string;
1676
+ /** Restrict to one conversation-session key. */
1677
+ sessionKey?: string;
1437
1678
  /** Inclusive lower bound (epoch ms). */
1438
1679
  from?: number;
1439
1680
  /** Inclusive upper bound (epoch ms). */
@@ -1458,9 +1699,9 @@ interface AuditStatsQuery {
1458
1699
  * auditQueryApi — the AUTHED `GET /admin/api/audit?keyId=&from=&to=&limit=`
1459
1700
  * handler (request-audit-log, design D6).
1460
1701
  *
1461
- * Audit records carry client IP / user-agent (PII) and, when body capture is on,
1462
- * redacted bodies — so unlike the coarse `/health` boolean they are served ONLY
1463
- * behind the admin auth gate. This lives in its OWN helper module (the
1702
+ * Audit records carry client IP / user-agent (PII), and the sibling body query
1703
+ * returns redacted request/response snapshots — so unlike the coarse `/health`
1704
+ * boolean they are served ONLY behind the admin auth gate. This lives in its OWN helper module (the
1464
1705
  * #4/#8/#10 helper-module convention) so `adminApi.ts` — at its line cap — is not
1465
1706
  * touched: `AdminServer.dispatch` routes the path here directly, AFTER its auth
1466
1707
  * gate. NEVER unauthenticated, NEVER surfaced on `/health`.
@@ -1475,6 +1716,14 @@ interface AuditStatsQuery {
1475
1716
  type AuditQueryReader = (query: AuditQuery) => AuditRecord[];
1476
1717
  /** Metadata-only aggregate reader used by the overview. */
1477
1718
  type AuditStatsReader = (query: AuditStatsQuery) => Promise<AuditStats> | AuditStats;
1719
+ /** Reconstructs ONE record's bodies from the per-session shard store. */
1720
+ type AuditBodyReader = (query: AuditBodyQuery) => AuditBodyResult;
1721
+ /** Runs the cross-session body compaction over every closed day. */
1722
+ type AuditCompactor = () => {
1723
+ days: number;
1724
+ shards: number;
1725
+ savedBytes: number;
1726
+ };
1478
1727
 
1479
1728
  /**
1480
1729
  * billingStatusApi — the AUTHED `GET /admin/api/billing-status` handler
@@ -1493,6 +1742,254 @@ type AuditStatsReader = (query: AuditStatsQuery) => Promise<AuditStats> | AuditS
1493
1742
  /** The read surface the AdminServer consumes (bootstrap binds it to the ledger dir). */
1494
1743
  type BillingStatusReader = () => BillingDeliveryStatus;
1495
1744
 
1745
+ type PreparedImageRuntimeGeneration = {
1746
+ readonly id: string;
1747
+ readonly enabled: true;
1748
+ readonly imageApi: ImageApiContributions;
1749
+ readonly hosted: ResponsesImageGenerationContribution;
1750
+ readonly hostedRuntime: HostedImageRuntimePolicy;
1751
+ readonly inspectCapability?: (apiKeyId: string) => Promise<Omit<ImageRuntimeCapabilityInspection, 'generationId'>>;
1752
+ readonly readRuntimeStatus?: () => ImageRuntimeResourceStatus;
1753
+ dispose(): void | Promise<void>;
1754
+ } | {
1755
+ readonly id: string;
1756
+ readonly enabled: false;
1757
+ dispose(): void | Promise<void>;
1758
+ };
1759
+ interface HostedImageRuntimeGenerationLease {
1760
+ readonly generationId: string;
1761
+ /** Compatibility/debug view; callers should prefer the deep methods below. */
1762
+ readonly contribution: ResponsesImageGenerationContribution;
1763
+ inspectRequest(input: ResponsesImageInspectionInput): ResponsesImageAdmission;
1764
+ validateSelection(admission: ResponsesImageAdmission, selection: ResponsesHostedToolSelection): void;
1765
+ openRequest(input: HostedImageOpenRequestInput): Promise<ResponsesImageRequestScope>;
1766
+ release(): Promise<void>;
1767
+ }
1768
+ interface HostedImageRuntimePolicy {
1769
+ readonly providerId: string;
1770
+ readonly imageModel: string;
1771
+ readonly referenceTtlMs: number;
1772
+ readonly maxOutputBytes: number;
1773
+ readonly maxTotalOutputBytes: number;
1774
+ readonly preferredAccountId?: string;
1775
+ readonly preferredAccountGroup?: string;
1776
+ readonly boundAccountFallbackPolicy?: 'strict' | 'pool';
1777
+ }
1778
+ interface HostedImageOpenRequestInput {
1779
+ readonly admission: ResponsesImageAdmission;
1780
+ readonly tenantId: string;
1781
+ readonly requestId: string;
1782
+ readonly sessionKey: string;
1783
+ readonly signal: AbortSignal;
1784
+ readonly authorizedPreviousResponseId?: string;
1785
+ /** Trusted affinity fact forwarded unchanged into the contribution scope. */
1786
+ readonly authorizedPreviousResponseKnownEmpty?: boolean;
1787
+ readonly mainProviderId: string;
1788
+ readonly selectedMainAccountId?: string;
1789
+ }
1790
+ /** Dormant integration seam for a later Native Responses owner. */
1791
+ interface HostedImageContributionFactory {
1792
+ acquire(): Promise<HostedImageRuntimeGenerationLease>;
1793
+ }
1794
+ /** Bind a stable factory to one app-session runtime manager without acquiring. */
1795
+ declare function createHostedImageContributionFactory(manager: ImageRuntimeManager): HostedImageContributionFactory;
1796
+ interface ImageRuntimeGenerationStatus {
1797
+ readonly generationId: string;
1798
+ readonly enabled: boolean;
1799
+ readonly httpLeases: number;
1800
+ readonly hostedLeases: number;
1801
+ }
1802
+ interface ImageRuntimeManagerStatus {
1803
+ readonly disposed: boolean;
1804
+ readonly current: ImageRuntimeGenerationStatus;
1805
+ readonly draining: readonly ImageRuntimeGenerationStatus[];
1806
+ }
1807
+ type ImageRuntimeSafeUnavailableReason = ImageCapabilityUnavailableReason | 'disabled' | 'runtime_unavailable';
1808
+ interface ImageRuntimeCapabilityInspection {
1809
+ readonly generationId: string;
1810
+ readonly enabled: boolean;
1811
+ readonly available: boolean;
1812
+ readonly providerId?: 'codex-subscription';
1813
+ readonly model?: string;
1814
+ readonly reason?: ImageRuntimeSafeUnavailableReason;
1815
+ readonly capabilities?: ImageCapabilities;
1816
+ }
1817
+ interface ImageRuntimeResourceStatus {
1818
+ readonly queue: Readonly<{
1819
+ activeJobs: number;
1820
+ waitingJobs: number;
1821
+ activeAccounts: number;
1822
+ waitingAccounts: number;
1823
+ waitingTenants: number;
1824
+ maxConcurrentJobsPerAccount: number;
1825
+ maxQueuedJobs: number;
1826
+ accepting: boolean;
1827
+ shuttingDown: boolean;
1828
+ }>;
1829
+ readonly temporary: Readonly<{
1830
+ activeScopes: number;
1831
+ totalBytes: number;
1832
+ tenantCount: number;
1833
+ maxActiveScopes: number;
1834
+ maxTotalBytes: number;
1835
+ maxTenantBytes: number;
1836
+ }>;
1837
+ readonly storage: Readonly<{
1838
+ mounts: number;
1839
+ retiredMounts: number;
1840
+ referenceEntries: number;
1841
+ referenceBytes: number;
1842
+ referenceTombstones: number;
1843
+ stateCalls: number;
1844
+ stateResponses: number;
1845
+ stateTombstones: number;
1846
+ pendingReferenceDeletes: number;
1847
+ maxReferenceEntries: number;
1848
+ maxReferenceBytes: number;
1849
+ maxTenantReferenceBytes: number;
1850
+ maxStateCalls: number;
1851
+ maxStateResponses: number;
1852
+ }>;
1853
+ }
1854
+ interface PreparedImageRuntimeChange {
1855
+ readonly generationId: string;
1856
+ publish(): void;
1857
+ rollback(): void;
1858
+ dispose(): Promise<void>;
1859
+ }
1860
+ /** App-session owner for stable forwarders and generation-pinned work. */
1861
+ declare class ImageRuntimeManager {
1862
+ #private;
1863
+ readonly contributions: ImageApiContributions;
1864
+ constructor(initial?: PreparedImageRuntimeGeneration);
1865
+ prepare(generation: PreparedImageRuntimeGeneration): PreparedImageRuntimeChange;
1866
+ acquireHosted(): Promise<HostedImageRuntimeGenerationLease>;
1867
+ inspectCapability(apiKeyId: string): Promise<ImageRuntimeCapabilityInspection>;
1868
+ listAvailableModels(apiKeyId: string): Promise<readonly string[]>;
1869
+ resourceStatus(): ImageRuntimeResourceStatus | undefined;
1870
+ status(): ImageRuntimeManagerStatus;
1871
+ dispose(): Promise<void>;
1872
+ }
1873
+
1874
+ type SafeProvider = 'codex-subscription' | 'unknown' | 'other';
1875
+ type SafeModel = 'gpt-image-2' | 'unknown' | 'other';
1876
+ type SafeErrorCode = ImageGenerationErrorCode | 'none' | 'other';
1877
+ type SafeCountOption = 'unknown' | '0' | '1' | '2-4' | '5+';
1878
+ type SafeBooleanOption = boolean | 'unknown';
1879
+ type SafeQuality = 'auto' | 'low' | 'medium' | 'high' | 'unknown' | 'other';
1880
+ type SafeBackground = 'auto' | 'opaque' | 'transparent' | 'unknown' | 'other';
1881
+ type SafeOutputFormat = 'png' | 'jpeg' | 'webp' | 'unknown' | 'other';
1882
+ declare const IMAGE_CONFIGURATION_AUDIT_FIELDS: readonly ["enablement", "provider", "model", "account", "queue", "temporary", "limits", "retention", "storage", "remote", "evidence"];
1883
+ type ImageConfigurationAuditField = typeof IMAGE_CONFIGURATION_AUDIT_FIELDS[number];
1884
+ /** Values are deliberately limited to safe categories and internal generation ids. */
1885
+ interface ImageConfigurationAuditRecord {
1886
+ readonly outcome: 'applied';
1887
+ readonly fields: readonly ImageConfigurationAuditField[];
1888
+ readonly previousGenerationId?: string;
1889
+ readonly generationId?: string;
1890
+ }
1891
+ interface ImageHistogramSnapshot {
1892
+ readonly count: number;
1893
+ readonly sum: number;
1894
+ /** Non-cumulative fixed buckets; `upperBound:null` is the overflow bucket. */
1895
+ readonly buckets: readonly {
1896
+ readonly upperBound: number | null;
1897
+ readonly count: number;
1898
+ }[];
1899
+ }
1900
+ interface ImageApiMetricDimensions {
1901
+ readonly endpoint: 'images.generate' | 'images.edit';
1902
+ readonly provider: SafeProvider;
1903
+ readonly model: SafeModel;
1904
+ readonly action: 'generate' | 'edit';
1905
+ readonly quality: SafeQuality;
1906
+ readonly background: SafeBackground;
1907
+ readonly outputFormat: SafeOutputFormat;
1908
+ readonly streaming: SafeBooleanOption;
1909
+ readonly requestedOutputs: SafeCountOption;
1910
+ readonly partialImages: SafeCountOption;
1911
+ readonly terminal: 'completed' | 'failed' | 'cancelled';
1912
+ readonly errorCode: SafeErrorCode;
1913
+ }
1914
+ interface ImageExecutionMetricDimensions {
1915
+ readonly provider: SafeProvider;
1916
+ readonly model: SafeModel;
1917
+ readonly action: 'generate' | 'edit' | 'other';
1918
+ readonly quality: SafeQuality;
1919
+ readonly background: SafeBackground;
1920
+ readonly outputFormat: SafeOutputFormat;
1921
+ readonly streaming: SafeBooleanOption;
1922
+ readonly requestedOutputs: SafeCountOption;
1923
+ readonly terminal: 'completed' | 'failed' | 'cancelled' | 'other';
1924
+ readonly errorCode: SafeErrorCode;
1925
+ }
1926
+ interface ImageApiMetricSnapshot {
1927
+ readonly dimensions: ImageApiMetricDimensions;
1928
+ readonly requests: number;
1929
+ readonly inputCount?: ImageHistogramSnapshot;
1930
+ readonly inputBytes?: ImageHistogramSnapshot;
1931
+ readonly referenceOutcomes: Readonly<{
1932
+ hits: number;
1933
+ notFound: number;
1934
+ expired: number;
1935
+ failed: number;
1936
+ }>;
1937
+ readonly cleanupOutcomes: Readonly<{
1938
+ completed: number;
1939
+ failed: number;
1940
+ }>;
1941
+ }
1942
+ interface ImageExecutionMetricSnapshot {
1943
+ readonly dimensions: ImageExecutionMetricDimensions;
1944
+ readonly executions: number;
1945
+ readonly finalLatencyMs: ImageHistogramSnapshot;
1946
+ readonly queueWaitMs?: ImageHistogramSnapshot;
1947
+ readonly generationDurationMs?: ImageHistogramSnapshot;
1948
+ readonly firstPartialLatencyMs?: ImageHistogramSnapshot;
1949
+ readonly inputCount: ImageHistogramSnapshot;
1950
+ readonly inputBytes: ImageHistogramSnapshot;
1951
+ readonly outputCount: ImageHistogramSnapshot;
1952
+ readonly outputBytes: ImageHistogramSnapshot;
1953
+ readonly retryCount?: ImageHistogramSnapshot;
1954
+ readonly authRefreshCount?: ImageHistogramSnapshot;
1955
+ readonly referenceSaveCount?: ImageHistogramSnapshot;
1956
+ readonly retentionRollbackFailures?: ImageHistogramSnapshot;
1957
+ }
1958
+ interface ImageObservabilitySnapshot {
1959
+ readonly apiRequests: readonly ImageApiMetricSnapshot[];
1960
+ readonly executions: readonly ImageExecutionMetricSnapshot[];
1961
+ readonly configurationChanges: readonly ImageConfigurationAuditRecord[];
1962
+ readonly overflow: Readonly<{
1963
+ apiRecords: number;
1964
+ telemetryRecords: number;
1965
+ }>;
1966
+ }
1967
+ interface ImageObservabilityOptions {
1968
+ /** Hard cap applied independently to API and execution dimension maps. */
1969
+ readonly maxDimensionSets?: number;
1970
+ }
1971
+ /** Process-local, metadata-only aggregation for HTTP and hosted Images work. */
1972
+ declare class ImageObservability {
1973
+ #private;
1974
+ readonly telemetrySink: ImageTelemetrySink;
1975
+ readonly audit: (record: ImageApiAuditRecord) => void;
1976
+ constructor(options?: ImageObservabilityOptions);
1977
+ recordApiAudit(record: ImageApiAuditRecord): void;
1978
+ recordTelemetry(record: ImageTelemetryRecord): void;
1979
+ recordConfigurationAudit(record: ImageConfigurationAuditRecord): void;
1980
+ snapshot(): ImageObservabilitySnapshot;
1981
+ reset(): void;
1982
+ }
1983
+
1984
+ interface PreparedServerConfigChange {
1985
+ /** Publish the prepared snapshot. Implementations should make this an infallible swap. */
1986
+ publish(): void | Promise<void>;
1987
+ /** Restore the exact runtime snapshot that preceded publish. */
1988
+ rollback(): void | Promise<void>;
1989
+ /** Release an unpublished or rolled-back replacement. */
1990
+ dispose(): void | Promise<void>;
1991
+ }
1992
+
1496
1993
  /**
1497
1994
  * autoDisableStore.ts — the daemon's PROCESS-IN-MEMORY auto-disable store.
1498
1995
  *
@@ -1632,9 +2129,16 @@ declare class ConfigFileProviderConfigSource implements ProviderConfigSource {
1632
2129
  * @module @omnicross/daemon/ports/JsonApiServerSettingsStore
1633
2130
  */
1634
2131
 
2132
+ interface JsonSettingsDocumentSnapshot {
2133
+ readonly existed: boolean;
2134
+ /** Raw persisted bytes; may contain encrypted secrets and must never enter a DTO or log. */
2135
+ readonly bytes?: Uint8Array;
2136
+ }
2137
+ type AtomicDocumentReplace = (targetPath: string, contents: Uint8Array) => void;
1635
2138
  declare class JsonApiServerSettingsStore implements ApiServerSettingsStore {
1636
2139
  private readonly configPath;
1637
2140
  private readonly box;
2141
+ private readonly atomicReplace;
1638
2142
  /**
1639
2143
  * @param configPath the daemon config.json whose `server` field is backed.
1640
2144
  * @param box OPTIONAL at-rest `SecretBox` (upstream-proxy). When set, the
@@ -1643,9 +2147,13 @@ declare class JsonApiServerSettingsStore implements ApiServerSettingsStore {
1643
2147
  * secret-aware — every OTHER server field is non-secret). Null
1644
2148
  * ⇒ passthrough (legacy/pure tests unchanged).
1645
2149
  */
1646
- constructor(configPath: string, box?: SecretBox | null);
2150
+ constructor(configPath: string, box?: SecretBox | null, atomicReplace?: AtomicDocumentReplace);
1647
2151
  get<T = unknown>(key: string): Promise<T | undefined>;
1648
2152
  set<T = unknown>(key: string, value: T): Promise<void>;
2153
+ /** Capture the exact prior document for an admin transaction rollback. */
2154
+ captureDocumentSnapshot(): JsonSettingsDocumentSnapshot;
2155
+ /** Restore exact prior bytes (including unrelated fields and encrypted secrets). */
2156
+ restoreDocumentSnapshot(snapshot: JsonSettingsDocumentSnapshot): void;
1649
2157
  /** Encrypt the proxy passwords + webhook + billing secrets before persisting (no-op without a box). */
1650
2158
  private encryptSecrets;
1651
2159
  /** Decrypt the proxy passwords + webhook + billing secrets on read (no-op without a box). */
@@ -1727,7 +2235,31 @@ declare class JsonPricingStore implements PricingStore {
1727
2235
  private replaceFile;
1728
2236
  }
1729
2237
 
2238
+ interface CodexAuthHelperConfig {
2239
+ command: string;
2240
+ args: string[];
2241
+ }
2242
+
1730
2243
  type IntegrationClientId = 'codex' | 'claude';
2244
+ type IntegrationKeyOwnership = 'managed' | 'selected';
2245
+ /** Secret-free pointer to an access key. The plaintext remains in the encrypted key store. */
2246
+ interface IntegrationKeyBinding {
2247
+ keyId: string;
2248
+ ownership: IntegrationKeyOwnership;
2249
+ }
2250
+ /** Redacted access-key state exposed by the integrations admin API. */
2251
+ interface IntegrationKeyBindingStatus {
2252
+ id: string;
2253
+ name: string;
2254
+ keyPrefix: string;
2255
+ ownership: IntegrationKeyOwnership;
2256
+ revealable: boolean;
2257
+ enabled: boolean;
2258
+ revoked: boolean;
2259
+ allowedEndpoints: OutboundPermission[];
2260
+ requiredEndpoints: OutboundPermission[];
2261
+ loopbackOnly: boolean;
2262
+ }
1731
2263
  type IntegrationStatusKind = 'not-installed' | 'enabled' | 'configuration-drift' | 'configuration-missing' | 'key-missing';
1732
2264
  interface IntegrationClientStatus {
1733
2265
  client: IntegrationClientId;
@@ -1736,6 +2268,8 @@ interface IntegrationClientStatus {
1736
2268
  installedAt?: number;
1737
2269
  gatewayBaseUrl?: string;
1738
2270
  message?: string;
2271
+ /** Selected key metadata only; never contains plaintext or the encrypted envelope. */
2272
+ key?: IntegrationKeyBindingStatus;
1739
2273
  }
1740
2274
  interface IntegrationChangePlan {
1741
2275
  client: IntegrationClientId;
@@ -1775,7 +2309,9 @@ interface IntegrationGatewayKeyRecord {
1775
2309
  }
1776
2310
  interface IntegrationState {
1777
2311
  version: 1;
2312
+ /** Legacy shared-key layout. New installs use `keyBindings`; retained for safe migration. */
1778
2313
  gatewayKey?: IntegrationGatewayKeyRecord;
2314
+ keyBindings?: Partial<Record<IntegrationClientId, IntegrationKeyBinding>>;
1779
2315
  clients: Partial<Record<IntegrationClientId, IntegrationInstallRecord>>;
1780
2316
  }
1781
2317
 
@@ -1793,27 +2329,38 @@ interface IntegrationManagerOptions {
1793
2329
  gatewayBaseUrl: string;
1794
2330
  keyDb: OutboundKeyDb;
1795
2331
  stateStore: IntegrationStateStore;
2332
+ codexAuthHelper?: CodexAuthHelperConfig;
1796
2333
  homeDir?: string;
1797
2334
  }
1798
- /** Coordinates a least-privilege gateway key with reversible native CLI config edits. */
2335
+ /** Coordinates per-client least-privilege keys with reversible native CLI config edits. */
1799
2336
  declare class IntegrationManager {
1800
2337
  private readonly options;
1801
2338
  private readonly homeDir;
2339
+ private readonly codexAuthHelper;
1802
2340
  constructor(options: IntegrationManagerOptions);
1803
2341
  listStatus(): Promise<IntegrationClientStatus[]>;
1804
2342
  plan(client: IntegrationClientId, configPath?: string): Promise<IntegrationChangePlan>;
1805
2343
  install(client: IntegrationClientId, configPath?: string): Promise<IntegrationClientStatus>;
1806
2344
  repair(client: IntegrationClientId): Promise<IntegrationClientStatus>;
1807
2345
  remove(client: IntegrationClientId): Promise<IntegrationClientStatus>;
2346
+ /** Bind a user-confirmed access key and grant only this client's required endpoints. */
2347
+ bindIntegrationKey(client: IntegrationClientId, keyId: string): Promise<IntegrationClientStatus>;
2348
+ /** Rotate every Omnicross-managed client binding; user-selected keys remain untouched. */
1808
2349
  rotateGatewayKey(): Promise<{
1809
- keyId: string;
2350
+ keyIds: Partial<Record<IntegrationClientId, string>>;
1810
2351
  }>;
1811
- getGatewayToken(): Promise<string>;
1812
- private ensureGatewayKey;
1813
- private isKeyUsable;
2352
+ /** Resolve the plaintext only for the command-auth helper; callers must not log it. */
2353
+ getIntegrationToken(client: IntegrationClientId): Promise<string>;
2354
+ /** Compatibility alias for callers predating per-client bindings. */
2355
+ getGatewayToken(client?: IntegrationClientId): Promise<string>;
2356
+ private ensureClientKey;
2357
+ private createManagedClientKey;
2358
+ private installedSecret;
2359
+ private rebindInstalledClient;
1814
2360
  private statusFor;
2361
+ private boundKeyDetails;
2362
+ private retireManagedKeys;
1815
2363
  private defaultConfigPath;
1816
- private codexAuthPathForConfig;
1817
2364
  private renderInstalled;
1818
2365
  }
1819
2366
 
@@ -1933,6 +2480,11 @@ interface PoolKeyHealth {
1933
2480
  interface PoolHealthReader {
1934
2481
  getKeyHealth(providerId: string): Promise<Record<string, PoolKeyHealth>>;
1935
2482
  }
2483
+ interface AdminImagesStatusReader {
2484
+ inspectCapability(apiKeyId: string): Promise<ImageRuntimeCapabilityInspection>;
2485
+ status(): ImageRuntimeManagerStatus;
2486
+ resourceStatus(): ImageRuntimeResourceStatus | undefined;
2487
+ }
1936
2488
  /** The live daemon handles the management API operates over. */
1937
2489
  interface AdminApiDeps {
1938
2490
  /** Path to the daemon's `config.json` (provider catalog + `server` field). */
@@ -1957,6 +2509,16 @@ interface AdminApiDeps {
1957
2509
  readonly settingsStore: JsonApiServerSettingsStore;
1958
2510
  /** The running outbound server (status + live applyConfig). */
1959
2511
  readonly outboundApiServer: OutboundApiServer;
2512
+ /** True only when production composed the hardened per-hop remote resolver. */
2513
+ readonly imageRemoteResolverAvailable?: boolean;
2514
+ /** Optional production Images runtime generation participant. */
2515
+ readonly imageRuntimeConfig?: {
2516
+ prepareConfig(config: ImagesServerConfig): Promise<PreparedServerConfigChange>;
2517
+ };
2518
+ /** Narrow metadata-only reader for authenticated Images capability/status. */
2519
+ readonly imageRuntimeStatus?: AdminImagesStatusReader;
2520
+ /** Metadata-only successful Images configuration audit sink. */
2521
+ readonly imageConfigAudit?: (record: ImageConfigurationAuditRecord) => void;
1960
2522
  /** Process-local machine-managed routing leases (optional for light embedders). */
1961
2523
  readonly routeLeaseManager?: RouteLeaseManager;
1962
2524
  /** Subscription accounts (token-free `listAll`). */
@@ -2132,6 +2694,13 @@ interface AdminServerDeps extends AdminApiDeps {
2132
2694
  auditReader?: AuditQueryReader;
2133
2695
  /** Metadata-only audit aggregate used by the overview error-rate metric. */
2134
2696
  auditStatsReader?: AuditStatsReader;
2697
+ /**
2698
+ * Reconstructs one record's bodies from the per-session shard store
2699
+ * (audit-store-sharding). Absent when audit was never enabled.
2700
+ */
2701
+ auditBodyReader?: AuditBodyReader;
2702
+ /** Runs cross-session body compaction on demand (audit-store-sharding D8). */
2703
+ auditCompactor?: AuditCompactor;
2135
2704
  /**
2136
2705
  * OPTIONAL billing delivery-status reader (billing-event-stream, design D5).
2137
2706
  * When wired (bootstrap → the ledger dir), the AUTHED `GET /admin/api/billing-status`
@@ -2174,9 +2743,673 @@ declare class AdminServer {
2174
2743
  getStatus(): AdminServerStatus;
2175
2744
  }
2176
2745
 
2746
+ type DaemonImagePathArea = 'temporary' | 'artifacts' | 'state' | 'evidence' | 'mountManifest';
2747
+ interface DaemonImagePaths {
2748
+ readonly applicationDataRoot: string;
2749
+ readonly imagesRoot: string;
2750
+ readonly temporaryRoot: string;
2751
+ readonly durableRoot: string;
2752
+ readonly artifactsRoot: string;
2753
+ readonly stateRoot: string;
2754
+ readonly evidenceRoot: string;
2755
+ readonly mountManifestRoot: string;
2756
+ readonly mountManifestPath: string;
2757
+ }
2758
+ interface ImageRootValidationOptions {
2759
+ readonly label?: string;
2760
+ readonly processDirectory?: string;
2761
+ readonly userHome?: string;
2762
+ readonly temporaryDirectory?: string;
2763
+ }
2764
+ interface CreateDaemonImagePathResolverOptions extends ImageRootValidationOptions {
2765
+ readonly configPath: string;
2766
+ readonly storageRoot?: string;
2767
+ }
2768
+ interface VerifiedDaemonImagePath {
2769
+ readonly area: DaemonImagePathArea;
2770
+ readonly absolutePath: string;
2771
+ readonly kind: 'opaque-file' | 'opaque-directory' | 'mount-manifest';
2772
+ }
2773
+ /**
2774
+ * Owns all daemon Images filesystem names. Callers receive opaque capabilities,
2775
+ * never a filename-accepting delete primitive; destructive methods revalidate
2776
+ * the root identity, descendant relationship, basename, and symlink state.
2777
+ */
2778
+ declare class DaemonImagePathResolver {
2779
+ #private;
2780
+ readonly paths: DaemonImagePaths;
2781
+ constructor(options: CreateDaemonImagePathResolverOptions);
2782
+ createOpaqueFile(area: Exclude<DaemonImagePathArea, 'temporary' | 'mountManifest'>, format?: 'bin' | 'json' | 'tmp'): VerifiedDaemonImagePath;
2783
+ createOpaqueDirectory(area?: Exclude<DaemonImagePathArea, 'mountManifest'>): VerifiedDaemonImagePath;
2784
+ mountManifest(): VerifiedDaemonImagePath;
2785
+ /** Revalidate and return one internal root for store-local bounded I/O. */
2786
+ verifiedRoot(area: DaemonImagePathArea): string;
2787
+ /** Revalidate immediately before unlinking a resolver-issued file capability. */
2788
+ removeFile(target: VerifiedDaemonImagePath): void;
2789
+ /** Only empty opaque directories may be removed until the owned-marker layer is composed. */
2790
+ removeEmptyDirectory(target: VerifiedDaemonImagePath): void;
2791
+ private issue;
2792
+ private verifyDestructiveTarget;
2793
+ }
2794
+
2795
+ interface FileCodexImageCapabilityEvidenceManifestOwnerOptions {
2796
+ readonly paths: DaemonImagePathResolver;
2797
+ readonly maxEntries?: number;
2798
+ readonly now?: () => number;
2799
+ readonly random?: (bytes: number) => Buffer;
2800
+ readonly hmacSalt?: Uint8Array;
2801
+ readonly replaceManifest?: (targetPath: string, contents: Uint8Array) => void;
2802
+ }
2803
+ type FileCodexImageCapabilityEvidenceSourceOptions = Readonly<(FileCodexImageCapabilityEvidenceManifestOwnerOptions & {
2804
+ readonly ttlMs: number;
2805
+ }) | {
2806
+ readonly owner: FileCodexImageCapabilityEvidenceManifestOwner;
2807
+ readonly ttlMs: number;
2808
+ }>;
2809
+ interface FileCodexImageCapabilityEvidenceStatus {
2810
+ readonly entries: number;
2811
+ readonly freshEntries: number;
2812
+ readonly staleEntries: number;
2813
+ readonly bytes: number;
2814
+ }
2815
+ /** Revision-aware manifest owner shared by runtime generations, doctor, and cleanup. */
2816
+ declare class FileCodexImageCapabilityEvidenceManifestOwner {
2817
+ #private;
2818
+ constructor(options: FileCodexImageCapabilityEvidenceManifestOwnerOptions);
2819
+ createSource(ttlMs: number): FileCodexImageCapabilityEvidenceSource;
2820
+ resolveWithTtl(request: CodexImageCapabilityEvidenceRequest, ttlMs: number): Promise<CodexImageCapabilityEvidence>;
2821
+ recordSuccessfulVerificationWithTtl(observation: CodexImageCapabilityObservation, ttlMs: number): Promise<void>;
2822
+ cleanup(now: number, limit: number): Promise<{
2823
+ readonly entriesRemoved: number;
2824
+ readonly bytesRemoved: number;
2825
+ }>;
2826
+ statusWithTtl(ttlMs: number): FileCodexImageCapabilityEvidenceStatus;
2827
+ }
2828
+ /** Immutable TTL view over a revision-aware file-backed evidence manifest owner. */
2829
+ declare class FileCodexImageCapabilityEvidenceSource implements CodexImageCapabilityEvidenceSource {
2830
+ #private;
2831
+ constructor(options: FileCodexImageCapabilityEvidenceSourceOptions);
2832
+ createView(ttlMs: number): FileCodexImageCapabilityEvidenceSource;
2833
+ resolve(request: CodexImageCapabilityEvidenceRequest): Promise<CodexImageCapabilityEvidence>;
2834
+ recordSuccessfulVerification(observation: CodexImageCapabilityObservation): Promise<void>;
2835
+ cleanup(now: number, limit: number): Promise<{
2836
+ readonly entriesRemoved: number;
2837
+ readonly bytesRemoved: number;
2838
+ }>;
2839
+ status(): FileCodexImageCapabilityEvidenceStatus;
2840
+ ttlMs(): number;
2841
+ /** Lifecycle-symmetric no-op; physical safety no longer depends on local leases. */
2842
+ dispose(): void;
2843
+ }
2844
+
2845
+ interface FileImageReferenceStoreLimits {
2846
+ readonly ttlMs: number;
2847
+ readonly maxArtifactBytes: number;
2848
+ readonly maxTotalBytes: number;
2849
+ readonly maxTenantBytes: number;
2850
+ readonly maxEntries: number;
2851
+ readonly maxTombstones: number;
2852
+ readonly tombstoneTtlMs: number;
2853
+ }
2854
+ interface FileImageReferenceStoreOptions {
2855
+ readonly paths: DaemonImagePathResolver;
2856
+ readonly limits: FileImageReferenceStoreLimits;
2857
+ readonly secretBox?: SecretBox;
2858
+ readonly now?: () => number;
2859
+ readonly random?: (bytes: number) => Buffer;
2860
+ readonly replaceManifest?: (targetPath: string, contents: Uint8Array) => void;
2861
+ }
2862
+ interface FileImageReferenceReconciliationResult {
2863
+ readonly metadataRemoved: number;
2864
+ readonly metadataDegradedToProviderReference: number;
2865
+ readonly orphanFilesRemoved: number;
2866
+ readonly incompleteFilesRemoved: number;
2867
+ readonly invalidDescendants: number;
2868
+ }
2869
+ declare class FileImageReferenceStore implements ImageReferenceStore {
2870
+ #private;
2871
+ constructor(options: FileImageReferenceStoreOptions);
2872
+ save(input: ImageReferenceSaveInput): Promise<ImageReferenceMetadata>;
2873
+ /** Generation-bound write entry point; reads and maintenance remain shared. */
2874
+ saveWithLimits(input: ImageReferenceSaveInput, limits: FileImageReferenceStoreLimits): Promise<ImageReferenceMetadata>;
2875
+ /** Updates only app-session maintenance policy; pinned writes pass their own limits. */
2876
+ updateMaintenanceLimits(limits: FileImageReferenceStoreLimits): void;
2877
+ resolve(tenantId: string, referenceId: ImageReferenceId): Promise<ImageReferenceResolution>;
2878
+ delete(tenantId: string, referenceId: ImageReferenceId): Promise<boolean>;
2879
+ /** Daemon-internal cleanup path; accepts only the local reference-domain tenant HMAC. */
2880
+ deleteByHashedTenantKey(tenantKey: string, referenceId: ImageReferenceId): Promise<boolean>;
2881
+ cleanup(now?: number): Promise<number>;
2882
+ status(): {
2883
+ readonly entries: number;
2884
+ readonly bytes: number;
2885
+ readonly tombstones: number;
2886
+ };
2887
+ hasLiveReferenceByHashedTenantKey(tenantKey: string, referenceId: ImageReferenceId, now?: number): Promise<boolean>;
2888
+ reconcileOwnedFiles(maxEntries: number): Promise<FileImageReferenceReconciliationResult>;
2889
+ openArtifact(fileName: string, byteLength: number, signal?: AbortSignal): Promise<ReadableStream<Uint8Array>>;
2890
+ private exclusive;
2891
+ private tenantKey;
2892
+ private newReferenceId;
2893
+ private validateSaveInput;
2894
+ private selectVictims;
2895
+ private nextTombstones;
2896
+ private writeArtifact;
2897
+ private artifactPath;
2898
+ private validArtifact;
2899
+ private removeArtifact;
2900
+ private safeUnlinkArtifactPath;
2901
+ private releaseLease;
2902
+ private manifestPath;
2903
+ private persist;
2904
+ private atomicReplace;
2905
+ private loadManifest;
2906
+ }
2907
+
2908
+ interface FileResponsesImageStateStoreLimits {
2909
+ readonly maxCalls: number;
2910
+ readonly maxResponses: number;
2911
+ readonly maxTombstones: number;
2912
+ readonly tombstoneTtlMs: number;
2913
+ }
2914
+ interface FileResponsesImageStateStoreOptions {
2915
+ readonly paths: DaemonImagePathResolver;
2916
+ readonly limits: FileResponsesImageStateStoreLimits;
2917
+ readonly now?: () => number;
2918
+ readonly random?: (bytes: number) => Buffer;
2919
+ readonly replaceManifest?: (targetPath: string, contents: Uint8Array) => void;
2920
+ }
2921
+ interface PendingResponsesImageReferenceDelete {
2922
+ readonly referenceTenantKey: string;
2923
+ readonly binding: ResponsesImageCallBinding;
2924
+ }
2925
+ /** Durable production implementation of the existing Responses image-state contract. */
2926
+ declare class FileResponsesImageStateStore implements ResponsesImageStateStore {
2927
+ #private;
2928
+ constructor(options: FileResponsesImageStateStoreOptions);
2929
+ commit(input: ResponsesImageStateCommitInput): Promise<readonly ResponsesImageCallBinding[]>;
2930
+ /** Generation-bound write entry point; reads and maintenance remain shared. */
2931
+ commitWithLimits(input: ResponsesImageStateCommitInput, limits: FileResponsesImageStateStoreLimits): Promise<readonly ResponsesImageCallBinding[]>;
2932
+ /** Updates only app-session maintenance policy; pinned commits pass their own limits. */
2933
+ updateMaintenanceLimits(limits: FileResponsesImageStateStoreLimits): void;
2934
+ resolveCall(tenantId: string, callId: ResponsesImageCallId): Promise<ResponsesImageCallResolution>;
2935
+ resolveResponse(tenantId: string, responseId: string): Promise<ResponsesImageResponseResolution>;
2936
+ deleteCall(tenantId: string, callId: ResponsesImageCallId): Promise<ResponsesImageCallBinding | undefined>;
2937
+ deleteResponse(tenantId: string, responseId: string): Promise<boolean>;
2938
+ cleanup(now?: number): Promise<readonly ResponsesImageCallBinding[]>;
2939
+ pendingReferenceDeletes(limit?: number): readonly PendingResponsesImageReferenceDelete[];
2940
+ acknowledgeReferenceDeletes(completed: readonly PendingResponsesImageReferenceDelete[]): Promise<number>;
2941
+ reconcileBrokenReferenceLinks(hasLiveReference: (referenceTenantKey: string, referenceId: ResponsesImageCallBinding['referenceId']) => Promise<boolean>, maxEntries: number): Promise<readonly ResponsesImageCallBinding[]>;
2942
+ status(): {
2943
+ readonly calls: number;
2944
+ readonly responses: number;
2945
+ readonly tombstones: number;
2946
+ readonly pendingReferenceDeletes: number;
2947
+ };
2948
+ private exclusive;
2949
+ private failure;
2950
+ private assertCommit;
2951
+ private tenantKey;
2952
+ private rememberTombstone;
2953
+ private enqueuePendingReferenceDelete;
2954
+ private hasTombstone;
2955
+ private prunedTombstones;
2956
+ private pruneTombstonesInPlace;
2957
+ private sameTombstones;
2958
+ private touch;
2959
+ private releaseCall;
2960
+ private releaseResponse;
2961
+ private manifestPath;
2962
+ private persist;
2963
+ private atomicReplace;
2964
+ private loadManifest;
2965
+ }
2966
+
2967
+ interface ImageStorageMountBackend {
2968
+ readonly id: string;
2969
+ readonly createdAt: number;
2970
+ readonly resolver: DaemonImagePathResolver;
2971
+ readonly references: FileImageReferenceStore;
2972
+ readonly responsesState: FileResponsesImageStateStore;
2973
+ }
2974
+ interface ImageStorageMountCatalogOptions {
2975
+ readonly pathOptions: Omit<CreateDaemonImagePathResolverOptions, 'storageRoot'>;
2976
+ readonly activeStorageRoot?: string;
2977
+ readonly referenceLimits: FileImageReferenceStoreLimits;
2978
+ readonly responsesStateLimits: FileResponsesImageStateStoreLimits;
2979
+ readonly secretBox?: SecretBox;
2980
+ readonly now?: () => number;
2981
+ readonly random?: (bytes: number) => Buffer;
2982
+ readonly replaceCatalog?: (targetPath: string, contents: Uint8Array) => void;
2983
+ readonly reconcileCorruptManifests?: boolean;
2984
+ }
2985
+ interface ImageStorageMountPolicy {
2986
+ readonly referenceLimits: FileImageReferenceStoreLimits;
2987
+ readonly responsesStateLimits: FileResponsesImageStateStoreLimits;
2988
+ }
2989
+ interface PreparedImageStorageMountActivation {
2990
+ readonly backend: ImageStorageMountBackend;
2991
+ publish(): ImageStorageMountBackend;
2992
+ rollback(): void;
2993
+ dispose(): void;
2994
+ }
2995
+ /** Owns the durable-root set independently from any one runtime generation. */
2996
+ declare class ImageStorageMountCatalog {
2997
+ #private;
2998
+ constructor(options: ImageStorageMountCatalogOptions);
2999
+ active(): ImageStorageMountBackend;
3000
+ mountsForRead(): readonly ImageStorageMountBackend[];
3001
+ status(): {
3002
+ readonly mounts: number;
3003
+ readonly retiredMounts: number;
3004
+ };
3005
+ startupReconciliationStatus(): {
3006
+ readonly corruptManifestsQuarantined: number;
3007
+ };
3008
+ utilization(): {
3009
+ readonly referenceEntries: number;
3010
+ readonly referenceBytes: number;
3011
+ readonly referenceTombstones: number;
3012
+ readonly stateCalls: number;
3013
+ readonly stateResponses: number;
3014
+ readonly stateTombstones: number;
3015
+ readonly pendingReferenceDeletes: number;
3016
+ };
3017
+ /** Pins a backend object until its owning runtime generation drains. */
3018
+ retainBackend(backend: ImageStorageMountBackend): () => void;
3019
+ activate(storageRoot?: string): ImageStorageMountBackend;
3020
+ /** Prepare a validated backend without changing the catalog's active mount. */
3021
+ prepareActivation(storageRoot?: string, policy?: ImageStorageMountPolicy): PreparedImageStorageMountActivation;
3022
+ retireEmptyMount(mountId: string): boolean;
3023
+ private createResolver;
3024
+ private createBackend;
3025
+ private applyMaintenancePolicy;
3026
+ private newMountId;
3027
+ private isManifestError;
3028
+ private quarantineManifest;
3029
+ private isVerifiedEmpty;
3030
+ private catalogPath;
3031
+ private persist;
3032
+ private atomicReplace;
3033
+ private loadCatalog;
3034
+ }
3035
+ declare class MountedImageReferenceStore implements ImageReferenceStore {
3036
+ private readonly catalog;
3037
+ private readonly writeBackend?;
3038
+ private readonly writeLimits?;
3039
+ constructor(catalog: ImageStorageMountCatalog, writeBackend?: ImageStorageMountBackend | undefined, writeLimits?: FileImageReferenceStoreLimits | undefined);
3040
+ bindWriteBackend(backend: ImageStorageMountBackend, limits: FileImageReferenceStoreLimits): MountedImageReferenceStore;
3041
+ status(): Readonly<{
3042
+ referenceEntries: number;
3043
+ referenceBytes: number;
3044
+ referenceTombstones: number;
3045
+ stateCalls: number;
3046
+ stateResponses: number;
3047
+ stateTombstones: number;
3048
+ pendingReferenceDeletes: number;
3049
+ mounts: number;
3050
+ retiredMounts: number;
3051
+ }>;
3052
+ save(input: ImageReferenceSaveInput): Promise<_omnicross_contracts_image_generation_types.ImageReferenceMetadata>;
3053
+ resolve(tenantId: string, referenceId: ImageReferenceId): Promise<ImageReferenceResolution>;
3054
+ delete(tenantId: string, referenceId: ImageReferenceId): Promise<boolean>;
3055
+ deleteByHashedTenantKey(tenantKey: string, referenceId: ImageReferenceId): Promise<boolean>;
3056
+ cleanup(now?: number): Promise<number>;
3057
+ }
3058
+ declare class MountedResponsesImageStateStore implements ResponsesImageStateStore {
3059
+ private readonly catalog;
3060
+ private readonly writeBackend?;
3061
+ private readonly writeLimits?;
3062
+ constructor(catalog: ImageStorageMountCatalog, writeBackend?: ImageStorageMountBackend | undefined, writeLimits?: FileResponsesImageStateStoreLimits | undefined);
3063
+ bindWriteBackend(backend: ImageStorageMountBackend, limits: FileResponsesImageStateStoreLimits): MountedResponsesImageStateStore;
3064
+ commit(input: ResponsesImageStateCommitInput): Promise<readonly ResponsesImageCallBinding[]>;
3065
+ resolveCall(tenantId: string, callId: ResponsesImageCallId): Promise<ResponsesImageCallResolution>;
3066
+ resolveResponse(tenantId: string, responseId: string): Promise<ResponsesImageResponseResolution>;
3067
+ deleteCall(tenantId: string, callId: ResponsesImageCallId): Promise<ResponsesImageCallBinding | undefined>;
3068
+ deleteResponse(tenantId: string, responseId: string): Promise<boolean>;
3069
+ cleanup(now?: number): Promise<readonly ResponsesImageCallBinding[]>;
3070
+ }
3071
+
3072
+ interface ImageDoctorLocalSnapshot {
3073
+ readonly config: Readonly<{
3074
+ enabled: boolean;
3075
+ provider: 'codex-subscription';
3076
+ model: string;
3077
+ valid: boolean;
3078
+ errorCount: number;
3079
+ }>;
3080
+ readonly roots: Readonly<{
3081
+ valid: boolean;
3082
+ verifiedAreas: number;
3083
+ expectedAreas: number;
3084
+ }>;
3085
+ readonly stores: Readonly<{
3086
+ valid: boolean;
3087
+ mounts: number;
3088
+ retiredMounts: number;
3089
+ referenceEntries: number;
3090
+ referenceBytes: number;
3091
+ stateCalls: number;
3092
+ stateResponses: number;
3093
+ corruptManifestsQuarantined: number;
3094
+ }>;
3095
+ readonly permissions: Readonly<{
3096
+ valid: boolean;
3097
+ rows: number;
3098
+ legacyRows: number;
3099
+ invalidRows: number;
3100
+ imagesAuthorizedRows: number;
3101
+ }>;
3102
+ readonly account: Readonly<{
3103
+ present: boolean;
3104
+ usable: boolean;
3105
+ reason: 'ready' | 'missing' | 'unavailable';
3106
+ }>;
3107
+ readonly evidence: Readonly<FileCodexImageCapabilityEvidenceStatus & {
3108
+ valid: boolean;
3109
+ }>;
3110
+ }
3111
+ type ImageDoctorLiveFailureCode = 'images_disabled' | 'codex_account_unavailable' | 'evidence_store_unavailable' | 'evidence_persist_failed' | ImageGenerationErrorCode;
3112
+ type ImageDoctorLiveResult = Readonly<{
3113
+ ok: true;
3114
+ code: 'verified';
3115
+ model: 'gpt-image-2';
3116
+ quality: 'low';
3117
+ outputFormat: 'png';
3118
+ freshEvidenceEntries: number;
3119
+ }> | Readonly<{
3120
+ ok: false;
3121
+ code: ImageDoctorLiveFailureCode;
3122
+ }>;
3123
+ interface ImageDoctorService {
3124
+ inspectLocal(config: ImagesServerConfig): Promise<ImageDoctorLocalSnapshot>;
3125
+ verifyLive(config: ImagesServerConfig, signal: AbortSignal): Promise<ImageDoctorLiveResult>;
3126
+ }
3127
+
3128
+ interface ImageTemporaryBudgetStatus {
3129
+ readonly activeScopes: number;
3130
+ readonly totalBytes: number;
3131
+ readonly tenantCount: number;
3132
+ }
3133
+ /** Process-local atomic budget shared by every scope in one pinned runtime generation. */
3134
+ declare class DaemonImageTemporaryBudget implements ImageTemporaryResourceBudget {
3135
+ #private;
3136
+ constructor(config: ImagesServerConfig['temporary']);
3137
+ acquireScope(tenantId: string): ImageTemporaryResourceBudgetLease;
3138
+ status(): ImageTemporaryBudgetStatus;
3139
+ }
3140
+ interface DaemonImageTemporaryResourceFactoryOptions {
3141
+ readonly paths: DaemonImagePathResolver;
3142
+ readonly config: ImagesServerConfig['temporary'];
3143
+ readonly activeScopes?: DaemonImageActiveScopeRegistry;
3144
+ }
3145
+ /** App-session registry shared by all runtime generations and recurring cleanup. */
3146
+ declare class DaemonImageActiveScopeRegistry {
3147
+ #private;
3148
+ constructor(paths: DaemonImagePathResolver);
3149
+ register(privateDirectory: string): () => void;
3150
+ isActive(privateDirectory: string): boolean;
3151
+ status(): {
3152
+ readonly activeDirectories: number;
3153
+ };
3154
+ }
3155
+ /** Binds the private root, fixed owner marker, tenant, and shared budget in one seam. */
3156
+ declare class DaemonImageTemporaryResourceFactory {
3157
+ #private;
3158
+ readonly budget: DaemonImageTemporaryBudget;
3159
+ constructor(options: DaemonImageTemporaryResourceFactoryOptions);
3160
+ readonly createResourceScope: (limits: ImageApiLimits, signal: AbortSignal, tenantId: string) => Promise<ImageRequestResourceScope>;
3161
+ }
3162
+
3163
+ interface ImageStartupReconcilerOptions {
3164
+ readonly catalog: ImageStorageMountCatalog;
3165
+ readonly temporaryPaths: DaemonImagePathResolver;
3166
+ readonly staleTemporaryAfterMs: number;
3167
+ readonly activeTemporaryScopes?: Pick<DaemonImageActiveScopeRegistry, 'isActive'>;
3168
+ readonly maxMountsPerPass?: number;
3169
+ readonly maxEntriesPerMount?: number;
3170
+ readonly maxTemporaryDirectoriesPerPass?: number;
3171
+ readonly now?: () => number;
3172
+ }
3173
+ interface ImageStartupReconciliationResult {
3174
+ readonly corruptManifestsQuarantined: number;
3175
+ readonly mountsVisited: number;
3176
+ readonly stateBindingsRemoved: number;
3177
+ readonly brokenBindingsRemoved: number;
3178
+ readonly referenceEntriesRemoved: number;
3179
+ readonly metadataRemoved: number;
3180
+ readonly metadataDegradedToProviderReference: number;
3181
+ readonly orphanFilesRemoved: number;
3182
+ readonly incompleteFilesRemoved: number;
3183
+ readonly transactionFilesRemoved: number;
3184
+ readonly temporaryDirectoriesRemoved: number;
3185
+ readonly foreignTemporaryDirectoriesSkipped: number;
3186
+ readonly activeTemporaryDirectoriesSkipped: number;
3187
+ readonly invalidDescendantsSkipped: number;
3188
+ readonly pendingReferenceDeletes: number;
3189
+ }
3190
+ /** One bounded, synchronous-filesystem startup pass; it never follows symlinks. */
3191
+ declare class ImageStartupReconciler {
3192
+ #private;
3193
+ constructor(options: ImageStartupReconcilerOptions);
3194
+ run(): Promise<ImageStartupReconciliationResult>;
3195
+ private hasLiveReference;
3196
+ private removeTransactionFiles;
3197
+ private removeStaleTemporaryDirectories;
3198
+ private removeTreeWithoutFollowingSymlinks;
3199
+ }
3200
+
3201
+ interface BoundedImageEvidenceCleanup {
3202
+ cleanup(now: number, limit: number): Promise<{
3203
+ readonly entriesRemoved: number;
3204
+ readonly bytesRemoved: number;
3205
+ }>;
3206
+ }
3207
+ interface ImageCleanupTimer {
3208
+ unref(): unknown;
3209
+ }
3210
+ interface ImageCleanupServiceOptions {
3211
+ readonly reconciler: ImageStartupReconciler;
3212
+ readonly catalog: ImageStorageMountCatalog;
3213
+ readonly intervalMs: number;
3214
+ readonly maxEvidenceEntriesPerPass?: number;
3215
+ readonly maxRetiredMountsPerPass?: number;
3216
+ readonly evidence?: BoundedImageEvidenceCleanup;
3217
+ readonly now?: () => number;
3218
+ readonly scheduleInterval?: (callback: () => void, intervalMs: number) => ImageCleanupTimer;
3219
+ readonly clearScheduledInterval?: (timer: ImageCleanupTimer) => void;
3220
+ }
3221
+ interface ImageCleanupPolicy {
3222
+ readonly reconciler: ImageStartupReconciler;
3223
+ readonly intervalMs: number;
3224
+ readonly evidence?: BoundedImageEvidenceCleanup;
3225
+ }
3226
+ interface PreparedImageCleanupPolicyChange {
3227
+ publish(): void;
3228
+ rollback(): void;
3229
+ dispose(): void;
3230
+ }
3231
+ interface ImageCleanupServicePassResult {
3232
+ readonly stateBindingsRemoved: number;
3233
+ readonly brokenBindingsRemoved: number;
3234
+ readonly referenceEntriesRemoved: number;
3235
+ readonly orphanFilesRemoved: number;
3236
+ readonly incompleteFilesRemoved: number;
3237
+ readonly transactionFilesRemoved: number;
3238
+ readonly temporaryDirectoriesRemoved: number;
3239
+ readonly evidenceEntriesRemoved: number;
3240
+ readonly evidenceBytesRemoved: number;
3241
+ readonly evidenceCleanupFailures: number;
3242
+ readonly retiredMountsRemoved: number;
3243
+ readonly referenceEntries: number;
3244
+ readonly referenceBytes: number;
3245
+ readonly referenceTombstones: number;
3246
+ readonly stateCalls: number;
3247
+ readonly stateResponses: number;
3248
+ readonly stateTombstones: number;
3249
+ readonly pendingReferenceDeletes: number;
3250
+ readonly finishedAt: number;
3251
+ }
3252
+ interface ImageCleanupServiceStatus extends ImageCleanupServicePassResult {
3253
+ readonly running: boolean;
3254
+ readonly passesCompleted: number;
3255
+ readonly passFailures: number;
3256
+ }
3257
+ /** Unref'ed recurring lifecycle owner with one non-overlapping bounded pass. */
3258
+ declare class ImageCleanupService {
3259
+ #private;
3260
+ constructor(options: ImageCleanupServiceOptions);
3261
+ start(): void;
3262
+ private schedule;
3263
+ /** Prepares a hot-reloadable cadence/reconciler/evidence snapshot. */
3264
+ preparePolicy(policy: ImageCleanupPolicy): PreparedImageCleanupPolicyChange;
3265
+ stop(): Promise<void>;
3266
+ reset(): Promise<void>;
3267
+ runOnce(): Promise<ImageCleanupServicePassResult>;
3268
+ status(): ImageCleanupServiceStatus;
3269
+ private performPass;
3270
+ }
3271
+
3272
+ interface TrustedImageApiRuntimeResolverOptions {
3273
+ readonly config: ImagesServerConfig;
3274
+ readonly referenceStore: ImageReferenceStore;
3275
+ /** Persistent private daemon key material. It is copied on construction. */
3276
+ readonly hmacKey: Uint8Array;
3277
+ /** Present only when the host has proved the complete remote-fetch policy. */
3278
+ readonly provenRemoteResolver?: RemoteImageAssetResolver;
3279
+ }
3280
+ interface TrustedImageApiRuntimeResolver {
3281
+ readonly resolve: ImageApiRuntimeResolver;
3282
+ dispose(): void;
3283
+ }
3284
+ /**
3285
+ * Resolves only daemon-authenticated route state. Named outbound keys retain
3286
+ * their stable key-id tenant; ephemeral Route Lease traffic falls back to the
3287
+ * authenticated lease id and remains isolated from other leases. Request
3288
+ * headers, including an inbound bearer, are deliberately outside the resolver's
3289
+ * identity inputs.
3290
+ */
3291
+ declare function createTrustedImageApiRuntimeResolver(options: TrustedImageApiRuntimeResolverOptions): TrustedImageApiRuntimeResolver;
3292
+
3293
+ interface DaemonImageExecutionSchedulerOptions {
3294
+ readonly config: ImagesServerConfig['queue'];
3295
+ /** Persistent private daemon key material; copied on construction. */
3296
+ readonly hmacKey: Uint8Array;
3297
+ }
3298
+ interface DaemonImageExecutionSchedulerStatus {
3299
+ readonly activeJobs: number;
3300
+ readonly waitingJobs: number;
3301
+ readonly activeAccounts: number;
3302
+ readonly waitingAccounts: number;
3303
+ readonly waitingTenants: number;
3304
+ readonly maxConcurrentJobsPerAccount: number;
3305
+ readonly maxQueuedJobs: number;
3306
+ readonly accepting: boolean;
3307
+ readonly shuttingDown: boolean;
3308
+ }
3309
+ /**
3310
+ * Snapshot-bound image admission with per-account active caps and fair tenant
3311
+ * rotation under one bounded global waiting population.
3312
+ */
3313
+ declare class DaemonImageExecutionScheduler implements ImageExecutionScheduler {
3314
+ #private;
3315
+ constructor(options: DaemonImageExecutionSchedulerOptions);
3316
+ deriveAccountKey(selectedAccountId: string): ImageExecutionAccountKey;
3317
+ acquire(request: ImageExecutionSchedulerRequest): Promise<ImageExecutionSchedulerGrant>;
3318
+ status(): DaemonImageExecutionSchedulerStatus;
3319
+ /** Stop new admissions while allowing already queued and active work to drain. */
3320
+ retire(): void;
3321
+ /** Reject waiters and cancel active grants. Safe to call repeatedly. */
3322
+ shutdown(): void;
3323
+ }
3324
+
3325
+ interface ImageRuntimeGenerationSharedStorage {
3326
+ /** Resolver associated with the catalog's already-active mount. */
3327
+ readonly paths: DaemonImagePathResolver;
3328
+ readonly referenceStore: MountedImageReferenceStore;
3329
+ readonly stateStore: MountedResponsesImageStateStore;
3330
+ }
3331
+ interface ImageRuntimeMetadataObservability {
3332
+ readonly telemetrySink?: ImageTelemetrySink;
3333
+ readonly audit?: (record: ImageApiAuditRecord) => void | Promise<void>;
3334
+ }
3335
+ /**
3336
+ * Explicit deterministic Tier-A seam. It replaces only the provider inside an
3337
+ * otherwise-production runtime generation and is never inferred from config.
3338
+ */
3339
+ interface SyntheticVerifiedImageProviderTestSeam {
3340
+ readonly label: 'synthetic-verified-image-provider-test-only';
3341
+ createProvider(context: Readonly<{
3342
+ generationId: string;
3343
+ scheduler: ImageExecutionScheduler;
3344
+ now: () => number;
3345
+ referenceStore: MountedImageReferenceStore;
3346
+ stateStore: MountedResponsesImageStateStore;
3347
+ }>): ImageProvider;
3348
+ }
3349
+ interface ImageRuntimeGenerationFactoryOptions {
3350
+ readonly generationId: string;
3351
+ readonly config: ImagesServerConfig;
3352
+ readonly subscriptionAccounts: Pick<SubscriptionAccountService, 'getStrategy'>;
3353
+ readonly storage: ImageRuntimeGenerationSharedStorage;
3354
+ readonly provenRemoteResolver?: RemoteImageAssetResolver;
3355
+ readonly observability?: ImageRuntimeMetadataObservability;
3356
+ readonly createRequestId?: () => string;
3357
+ readonly createCallId?: () => `ig_${string}`;
3358
+ readonly now?: () => number;
3359
+ /** Test seam; production loads the persistent daemon-local HMAC salt. */
3360
+ readonly privateHmacKey?: Uint8Array;
3361
+ /** App-session owner used to protect live directories across generations. */
3362
+ readonly activeTemporaryScopes?: DaemonImageActiveScopeRegistry;
3363
+ /** Immutable TTL view scoped to this runtime generation. */
3364
+ readonly evidenceSource?: FileCodexImageCapabilityEvidenceSource;
3365
+ /** App-session catalog lease retained until this generation is disposed. */
3366
+ readonly releaseStorageBackend?: () => void;
3367
+ /** Test-only Tier-A provider; production leaves this absent. */
3368
+ readonly testOnlySyntheticVerifiedProvider?: SyntheticVerifiedImageProviderTestSeam;
3369
+ }
3370
+ interface ProductionImageRuntimeComponents {
3371
+ readonly providerRegistry: ImageProviderRegistry;
3372
+ readonly orchestrator: ImageOrchestrator;
3373
+ readonly scheduler: DaemonImageExecutionScheduler;
3374
+ readonly evidenceSource: FileCodexImageCapabilityEvidenceSource;
3375
+ readonly temporaryResources: DaemonImageTemporaryResourceFactory;
3376
+ readonly runtimeResolver: TrustedImageApiRuntimeResolver;
3377
+ readonly referenceStore: MountedImageReferenceStore;
3378
+ readonly stateStore: MountedResponsesImageStateStore;
3379
+ }
3380
+ type ProductionImageRuntimeGeneration = PreparedImageRuntimeGeneration & {
3381
+ readonly components?: ProductionImageRuntimeComponents;
3382
+ };
3383
+ /**
3384
+ * Builds a complete generation without changing the storage catalog's active
3385
+ * mount. Catalog activation must be composed by a failure-atomic transaction.
3386
+ */
3387
+ declare function createImageRuntimeGeneration(options: ImageRuntimeGenerationFactoryOptions): ProductionImageRuntimeGeneration;
3388
+
3389
+ /**
3390
+ * JsonOutboundKeyDb — the daemon's file-backed `OutboundKeyDb` port impl
3391
+ * (design D3).
3392
+ *
3393
+ * Durable storage for named outbound API keys, backed by a json file (a sibling
3394
+ * of `config.json`, e.g. `keys.json`) holding an `OutboundKeyDbRow[]`. This port
3395
+ * provides ONLY storage — it never generates secrets nor hashes. Core's
3396
+ * `createNamedKey(db, name)` calls `outboundApiKeysCreate` with the sha256
3397
+ * `keyHash` + display `keyPrefix` and returns the one-time plaintext; the hot
3398
+ * auth path uses core's `hashKey(presented)` + `outboundApiKeysGetByHash`.
3399
+ *
3400
+ * OPTIONAL reversible storage: when constructed with a `SecretBox`, each created
3401
+ * key ALSO persists its plaintext as a `keySecret` `enc:` envelope, powering the
3402
+ * operator "view key" affordance (`outboundApiKeysReveal`). The hash remains the
3403
+ * auth index; without a box the store is hash-only (byte-identical to legacy).
3404
+ *
3405
+ * @module @omnicross/daemon/ports/JsonOutboundKeyDb
3406
+ */
3407
+
3408
+ type AtomicFileReplace = (targetPath: string, contents: string) => void;
2177
3409
  declare class JsonOutboundKeyDb implements OutboundKeyDb {
2178
3410
  private readonly keysPath;
2179
3411
  private readonly secretBox?;
3412
+ private readonly atomicReplace;
2180
3413
  /**
2181
3414
  * @param secretBox OPTIONAL reversible-secret codec. When present, a created
2182
3415
  * key's plaintext is persisted as a `keySecret` `enc:` envelope (enabling the
@@ -2185,7 +3418,7 @@ declare class JsonOutboundKeyDb implements OutboundKeyDb {
2185
3418
  * always returns `null`. Existing 1-arg call sites (tests, lightweight
2186
3419
  * embedders) keep working.
2187
3420
  */
2188
- constructor(keysPath: string, secretBox?: SecretBox | undefined);
3421
+ constructor(keysPath: string, secretBox?: SecretBox | undefined, atomicReplace?: AtomicFileReplace);
2189
3422
  outboundApiKeysList(): Promise<OutboundKeyDbRow[]>;
2190
3423
  outboundApiKeysGetByHash(hash: string): Promise<OutboundKeyDbRow | null>;
2191
3424
  outboundApiKeysCreate(input: {
@@ -2195,7 +3428,7 @@ declare class JsonOutboundKeyDb implements OutboundKeyDb {
2195
3428
  keyPrefix: string;
2196
3429
  createdAt?: number;
2197
3430
  kind?: 'client' | 'integration';
2198
- allowedEndpoints?: _omnicross_core.OutboundEndpoint[];
3431
+ allowedEndpoints?: OutboundPermission[];
2199
3432
  loopbackOnly?: boolean;
2200
3433
  plaintext?: string;
2201
3434
  }): Promise<OutboundKeyDbRow>;
@@ -2204,6 +3437,7 @@ declare class JsonOutboundKeyDb implements OutboundKeyDb {
2204
3437
  outboundApiKeysRevoke(id: string): Promise<boolean>;
2205
3438
  outboundApiKeysTouchLastUsed(id: string): Promise<boolean>;
2206
3439
  outboundApiKeysSetEnabled(id: string, enabled: boolean): Promise<boolean>;
3440
+ outboundApiKeysSetPermissions(id: string, permissions: OutboundPermission[]): Promise<boolean>;
2207
3441
  outboundApiKeysSetMaxConcurrency(id: string, maxConcurrency: number | null): Promise<boolean>;
2208
3442
  outboundApiKeysSetPolicy(id: string, policy: OutboundKeyPolicy): Promise<boolean>;
2209
3443
  outboundApiKeysMarkActivated(id: string, activatedAt: number): Promise<boolean>;
@@ -2295,17 +3529,28 @@ declare class AccountHealthSweeper {
2295
3529
  }
2296
3530
 
2297
3531
  /**
2298
- * AuditPruneSweeper — the TTL prune for the audit store (request-audit-log,
2299
- * design D4). Deletes whole `audit-YYYY-MM-DD.jsonl` files whose date is older
2300
- * than `retentionDays` — a cheap file UNLINK, never a line-level rewrite of a
2301
- * live file (which jsonl makes awkward). So the store never grows unbounded and
2302
- * TTL is O(files).
3532
+ * AuditPruneSweeper — retention + archiving for the audit store
3533
+ * (request-audit-log design D4, extended by audit-store-sharding design D6).
3534
+ *
3535
+ * Two jobs run on the same hourly tick:
3536
+ *
3537
+ * - PRUNE. Remove every day older than `retentionDays`. A current day is a
3538
+ * DIRECTORY (removed recursively), a legacy day is a flat file (unlinked with
3539
+ * its stats sidecar). Either way it stays a whole-day removal — never a
3540
+ * line-level rewrite of a live file, which jsonl makes awkward.
3541
+ * - ARCHIVE. First run the cross-session compaction pass (see
3542
+ * {@link compactAuditDay}), then gzip the body shards of any day that is no
3543
+ * longer today. The
3544
+ * current day stays PLAIN TEXT so it can still be tailed and grepped while
3545
+ * debugging; once a day is closed nothing appends to it again, so rewriting
3546
+ * and compressing it is safe and buys a large multiple on top of the delta.
3547
+ * `meta.jsonl` is NEVER compressed — it is the query hot path.
2303
3548
  *
2304
3549
  * Modeled on the #8 `AccountHealthProbeScheduler` / `AccountHealthSweeper`:
2305
3550
  * `start()` arms an `unref()`ed interval, `dispose()` clears it, a single-sweep
2306
- * re-entrancy guard prevents overlap. A prune ALSO runs once at boot (`start`
2307
- * fires an immediate sweep). Disabled/zero-retention config ⇒ armed-off no-op
2308
- * (byte-identical zero regression). Never throws.
3551
+ * re-entrancy guard prevents overlap. Both jobs also run once at boot. Disabled
3552
+ * config means armed-off, so audit-off is byte-identical zero regression. Never
3553
+ * throws.
2309
3554
  *
2310
3555
  * @module @omnicross/daemon/audit/AuditPruneSweeper
2311
3556
  */
@@ -2319,6 +3564,7 @@ declare class AuditPruneSweeper {
2319
3564
  private readonly now;
2320
3565
  private timer;
2321
3566
  private sweeping;
3567
+ private archiving;
2322
3568
  constructor(auditDir: string, logger: Logger, config: AuditConfig, intervalMs?: number,
2323
3569
  /** Injectable clock (ms) for deterministic tests. */
2324
3570
  now?: () => number);
@@ -2327,32 +3573,102 @@ declare class AuditPruneSweeper {
2327
3573
  /** Re-apply config to the live instance (boot + admin PUT hot-reload). */
2328
3574
  configure(config: AuditConfig): void;
2329
3575
  /**
2330
- * Arm the prune interval AND run one prune immediately (boot cleanup). No-op
2331
- * when audit is disabled (zero regression). Idempotent.
3576
+ * Arm the interval AND run one pass immediately (boot cleanup). No-op when
3577
+ * audit is disabled (zero regression). Idempotent.
2332
3578
  */
2333
3579
  start(): void;
2334
3580
  /** Clear the interval (daemon shutdown / test teardown). Idempotent. */
2335
3581
  dispose(): void;
3582
+ /** Prune first, then archive — never spend CPU compressing a day about to go. */
3583
+ private runOnce;
3584
+ /** The LOCAL-midnight epoch ms of the current day. */
3585
+ private todayMidnight;
2336
3586
  /**
2337
- * One prune: unlink every audit date file strictly OLDER than the retention
2338
- * cutoff (`now - retentionDays` days, at local-midnight granularity). Exposed
2339
- * for tests; never throws. Returns the number of files removed.
3587
+ * One prune: remove every audit day strictly OLDER than the retention cutoff
3588
+ * (`now - retentionDays` days, at local-midnight granularity). Exposed for
3589
+ * tests; never throws. Returns the number of days removed.
2340
3590
  */
2341
3591
  sweep(): Promise<number>;
3592
+ /**
3593
+ * Gzip the body shards of every CLOSED day (anything before today). Today is
3594
+ * deliberately left as plain text so it stays greppable while it is the day you
3595
+ * are debugging. Exposed for tests; never throws. Returns shards compressed.
3596
+ */
3597
+ archive(): Promise<number>;
3598
+ /** Gzip up to `budget` plain shards in one day's `bodies/` directory. */
3599
+ private archiveDay;
3600
+ }
3601
+
3602
+ /**
3603
+ * usageMigrate — the ONE-SHOT fold of the legacy flat `usage-events.jsonl` into
3604
+ * per-LOCAL-day shards plus their immutable rollups.
3605
+ *
3606
+ * Runs at bootstrap, BEFORE any listener is bound, so no request can be served
3607
+ * against a half-migrated store. It streams: the legacy file is never held in
3608
+ * memory as one string (that is the exact shape that put the old store on course
3609
+ * for V8's ~512 MB string ceiling), and each day's rollup is accumulated during
3610
+ * the same pass that writes its shard, so the whole migration is a single read.
3611
+ *
3612
+ * Writes are BATCHED per day (see {@link FLUSH_BYTES}). Awaiting a write per row
3613
+ * turned 199k rows into ~17 s of boot on a real 157 MB store, which the desktop
3614
+ * shell — waiting on the admin port — is right to find suspicious; batching cuts
3615
+ * it to a few seconds without changing what lands on disk.
3616
+ *
3617
+ * CRASH SAFETY. Everything is built inside `usage/.migrating/` and only moved
3618
+ * into place once the row count reconciles. A crash before the commit leaves the
3619
+ * legacy file untouched and the scratch directory to be wiped and rebuilt on the
3620
+ * next attempt. A crash DURING the commit leaves some shards in place and the
3621
+ * legacy file still present; the next attempt refuses to run (a target shard
3622
+ * already exists) and says so, rather than overwriting or double-counting rows.
3623
+ *
3624
+ * RECONCILIATION is the point of the exercise: `linesRead` must equal
3625
+ * `rowsWritten + skipped`, where `skipped` is lines the shared row guard rejects.
3626
+ * If it does not, nothing is committed and the legacy file stays exactly where it
3627
+ * was. Silently losing usage rows would corrupt lifetime key spend, which is the
3628
+ * one number in this store that can never be reconstructed.
3629
+ *
3630
+ * @module @omnicross/daemon/usage/usageMigrate
3631
+ */
3632
+
3633
+ interface UsageMigrationResult {
3634
+ /** True only when the legacy file was folded in and removed. */
3635
+ migrated: boolean;
3636
+ /** Non-empty lines read from the legacy file. */
3637
+ linesRead: number;
3638
+ /** Rows that passed the guard and were written to a shard. */
3639
+ rowsWritten: number;
3640
+ /** Lines the guard rejected (torn tail, hand-edited, foreign writer). */
3641
+ skipped: number;
3642
+ /** Distinct LOCAL days produced. */
3643
+ days: number;
3644
+ /** Present when the migration declined or aborted; nothing was committed. */
3645
+ reason?: string;
2342
3646
  }
2343
3647
 
2344
3648
  /**
2345
- * AuditWriter — the daemon's file-backed audit sink (request-audit-log, design
2346
- * D4/D5). Registered as `@omnicross/core`'s audit sink when audit is enabled; its
2347
- * {@link record} is what `recordAudit` hands each assembled record to.
3649
+ * AuditWriter — the daemon's file-backed audit sink (request-audit-log design
3650
+ * D4/D5, re-laid-out by audit-store-sharding design D2). Registered as
3651
+ * `@omnicross/core`'s audit sink when audit is enabled; its {@link record} is
3652
+ * what `recordAudit` hands each assembled record to.
2348
3653
  *
2349
- * FIRE-AND-FORGET (hard constraint): {@link record} DEFERS the fs append off the
3654
+ * FIRE-AND-FORGET (hard constraint): {@link record} DEFERS all fs work off the
2350
3655
  * caller's stack (an injectable `defer`, default a zero-delay timer — the
2351
3656
  * `UsageRecorder` precedent) and returns immediately, so the relay response path
2352
- * never waits on disk I/O. A write error is swallowed + logged (a failing audit
2353
- * store must never affect a relay). Each record is appended as ONE JSON line to
2354
- * `audit/audit-YYYY-MM-DD.jsonl` (the record's LOCAL date), matching the
2355
- * `usage-events.jsonl` pattern — no new dependency, TTL is a whole-file unlink.
3657
+ * never waits on disk I/O. Delta encoding rides that same deferred tick and is
3658
+ * pure memory, so it can never stall a response either. A write error is
3659
+ * swallowed + logged (a failing audit store must never affect a relay).
3660
+ *
3661
+ * Each record is split across TWO destinations under `audit/audit-YYYY-MM-DD/`:
3662
+ *
3663
+ * - `meta.jsonl` — one small JSON line WITHOUT bodies. This is what queries and
3664
+ * the stats sidecar read, so a listing no longer pays for megabytes of prompt.
3665
+ * - `bodies/<sessionKey>.jsonl` — the captured bodies, sharded per conversation
3666
+ * and delta-encoded against the session's previous turn.
3667
+ *
3668
+ * The two writes are INDEPENDENT: the metadata line is the canonical record, so a
3669
+ * body-shard failure is logged and the metadata still lands. When a shard write
3670
+ * fails the session's encoding base is dropped, otherwise the next turn would
3671
+ * chain a delta onto a base no reader can find.
2356
3672
  *
2357
3673
  * @module @omnicross/daemon/audit/AuditWriter
2358
3674
  */
@@ -2360,23 +3676,37 @@ declare class AuditPruneSweeper {
2360
3676
  declare class AuditWriter {
2361
3677
  private readonly auditDir;
2362
3678
  private readonly logger;
2363
- /** Deferral used by `record()` to schedule the append off the caller's path. */
3679
+ /** Deferral used by `record()` to schedule the writes off the caller's path. */
2364
3680
  private readonly defer;
2365
- private dirEnsured;
3681
+ /** Day directories already created this process (avoids an mkdir per record). */
3682
+ private readonly ensuredDirs;
3683
+ /** Per-session encoding bases. Memory-only; a miss simply writes a full snapshot. */
3684
+ private readonly bases;
2366
3685
  constructor(auditDir: string, logger: Logger,
2367
- /** Deferral used by `record()` to schedule the append off the caller's path. */
3686
+ /** Deferral used by `record()` to schedule the writes off the caller's path. */
2368
3687
  defer?: (fn: () => void) => void);
2369
3688
  /**
2370
- * Enqueue one record for append. Returns IMMEDIATELY (fire-and-forget); the fs
2371
- * write happens on the deferred tick. A failure is logged, never thrown.
3689
+ * Enqueue one record. Returns IMMEDIATELY (fire-and-forget); every fs write and
3690
+ * the delta encoding happen on the deferred tick. A failure is logged, never thrown.
2372
3691
  */
2373
3692
  record(record: AuditRecord): void;
3693
+ /** Drop all retained encoding bases (config reload / shutdown / test teardown). */
3694
+ reset(): void;
2374
3695
  /**
2375
- * Append synchronously — the awaitable form tests use to assert the line landed.
2376
- * Ensures the `audit/` directory exists on first write (lazy, like the usage
2377
- * store's lazy file creation).
3696
+ * Append synchronously — the awaitable form tests use to assert a line landed.
3697
+ * Writes the metadata line first (canonical), then the body shard.
2378
3698
  */
2379
3699
  appendNow(record: AuditRecord): void;
3700
+ /** Create a directory once per process and remember it. */
3701
+ private ensureDir;
3702
+ /** Write the body-free metadata line + refresh the exact-count sidecar. */
3703
+ private appendMeta;
3704
+ /**
3705
+ * Write the delta-encoded body shard for one record. A no-op when nothing was
3706
+ * captured or when the session key is missing/unsafe — in which case the body
3707
+ * is dropped rather than written to an unvalidated path.
3708
+ */
3709
+ private appendBody;
2380
3710
  }
2381
3711
 
2382
3712
  /**
@@ -2715,6 +4045,16 @@ interface DaemonPaths {
2715
4045
  * never invoke a real package manager. Absent → the real `exec`-based runner.
2716
4046
  */
2717
4047
  cliCommandRunner?: CommandRunner;
4048
+ /** TEST/COMPOSITION SEAM: prepared Images runtime generation for config transactions. */
4049
+ imageRuntimeConfig?: AdminApiDeps['imageRuntimeConfig'];
4050
+ /** TEST/COMPOSITION SEAM: metadata-only Images status reader. */
4051
+ imageRuntimeStatus?: AdminApiDeps['imageRuntimeStatus'];
4052
+ /** TEST/COMPOSITION SEAM: metadata-only successful Images config audit sink. */
4053
+ imageConfigAudit?: AdminApiDeps['imageConfigAudit'];
4054
+ /** TEST ONLY: deterministic Tier-A provider inside the production Images composition. */
4055
+ testOnlySyntheticVerifiedImageProvider?: SyntheticVerifiedImageProviderTestSeam;
4056
+ /** TEST SEAM: inject an atomic settings replacement fault. */
4057
+ settingsAtomicReplace?: (targetPath: string, contents: Uint8Array) => void;
2718
4058
  }
2719
4059
  /** The constructed daemon handles the CLI commands operate on. */
2720
4060
  interface Daemon {
@@ -2723,6 +4063,18 @@ interface Daemon {
2723
4063
  readonly llmConfig: ConfigFileProviderConfigSource;
2724
4064
  readonly keyDb: JsonOutboundKeyDb;
2725
4065
  readonly settingsStore: JsonApiServerSettingsStore;
4066
+ /** App-session extension-operation registry shared with the resident proxy. */
4067
+ readonly openAIOperationRegistry: OpenAIOperationRegistry;
4068
+ /** Stable Images forwarders and generation lifecycle owner for this app session. */
4069
+ readonly imageRuntimeManager: ImageRuntimeManager;
4070
+ /** Bounded process-local metadata aggregation shared by every runtime generation. */
4071
+ readonly imageObservability: ImageObservability;
4072
+ /** Startup reconciliation plus recurring bounded cleanup for Images state. */
4073
+ readonly imageCleanupService: ImageCleanupService;
4074
+ /** Local-only diagnostics plus the explicit consuming Images verifier. */
4075
+ readonly imageDoctor: ImageDoctorService;
4076
+ /** Generation-pinned hosted-image lease factory used by Native Responses. */
4077
+ readonly hostedImageContributionFactory: HostedImageContributionFactory;
2726
4078
  readonly providerProxy: ProviderProxy;
2727
4079
  readonly routeLeaseManager: RouteLeaseManager;
2728
4080
  readonly outboundApiServer: OutboundApiServer;
@@ -2800,6 +4152,17 @@ interface Daemon {
2800
4152
  * in cleanup.
2801
4153
  */
2802
4154
  readonly auditPruneSweeper: AuditPruneSweeper;
4155
+ /**
4156
+ * Retention for RAW usage rows (rollups are kept forever). Armed by `start`;
4157
+ * `launch` leaves it off — a short-lived boot has no business pruning.
4158
+ */
4159
+ readonly usagePruneSweeper: UsagePruneSweeper;
4160
+ /**
4161
+ * Fold a legacy flat `usage-events.jsonl` into day shards. MUST be awaited
4162
+ * before any listener binds: it moves files the query path reads, and a
4163
+ * request served mid-migration would see a partial store.
4164
+ */
4165
+ readonly migrateUsageStore: () => Promise<UsageMigrationResult>;
2803
4166
  /**
2804
4167
  * Durable-first billing publisher (billing-event-stream) — appends each event
2805
4168
  * to `billing/billing-YYYY-MM-DD.jsonl` FIRST, then best-effort POSTs it.
@@ -2816,11 +4179,6 @@ interface Daemon {
2816
4179
  */
2817
4180
  readonly billingRetrySweeper: BillingRetrySweeper;
2818
4181
  }
2819
- /**
2820
- * Construct the standalone daemon from a loaded config + on-disk paths. Does NOT
2821
- * start the listeners — the `start` command awaits `providerProxy.start()` then
2822
- * `outboundApiServer.applyConfig(...)`.
2823
- */
2824
4182
  declare function buildDaemon(config: DaemonConfig, paths: DaemonPaths): Daemon;
2825
4183
  /** Reset the core singletons (tests / teardown only). Re-exported for the suite.
2826
4184
  *
@@ -2906,11 +4264,24 @@ declare function buildHealthReport(deps: HealthReportDeps): HealthReport;
2906
4264
  * - SINK: always the console; PLUS an optional append-only file stream when
2907
4265
  * `file` is set (lazy-open; a write/open error is swallowed → the daemon never
2908
4266
  * crashes on a logging failure, it just falls back to the console).
4267
+ * - ROTATION: the file sink is size-capped (`maxFileBytes`, default 8 MB) and
4268
+ * keeps `maxFiles` generations (default 5) as `<file>.1` … `<file>.N`. An
4269
+ * UNBOUNDED append-only log is how a long-lived daemon quietly fills a disk,
4270
+ * so the cap is on by default rather than opt-in. Lines emitted mid-rotation
4271
+ * are queued (bounded) and flushed into the fresh generation, never dropped
4272
+ * silently unless the queue itself overflows.
2909
4273
  *
2910
4274
  * ZERO-REGRESSION DEFAULT: `new ConfigurableLogger()` (no config) = console +
2911
4275
  * all levels + text = behaviorally byte-identical to the legacy `ConsoleLogger`
2912
4276
  * (same `console` method per level, same `(message[, meta])` / error arg shape).
2913
4277
  *
4278
+ * NOTE — the daemon no longer CONSTRUCTS it that way. `bootstrap.ts` defaults the
4279
+ * file sink on (`<configDir>/logs/daemon.log`, level `info`, format `json`)
4280
+ * whenever the config omits it: the desktop app discards the daemon's stdout, so
4281
+ * a console-only logger meant a crash left no evidence at all. The unconfigured
4282
+ * CONSTRUCTOR default above is still console-only — only the daemon's wiring
4283
+ * changed.
4284
+ *
2914
4285
  * CAUTION (per the #3 host:port-only-logging precedent): the JSON serializer
2915
4286
  * reduces an `Error` to `{ message, stack }` and spreads a plain `meta` object,
2916
4287
  * but it is NOT a secret redactor — call sites remain responsible for not passing
@@ -2923,8 +4294,16 @@ declare class ConfigurableLogger implements Logger {
2923
4294
  private readonly threshold;
2924
4295
  private readonly format;
2925
4296
  private readonly filePath;
4297
+ private readonly maxFileBytes;
4298
+ private readonly maxFiles;
2926
4299
  private fileStream;
2927
4300
  private fileDisabled;
4301
+ /** Bytes in the CURRENT generation — seeded from the file on open. */
4302
+ private fileBytes;
4303
+ /** True from the moment a rotation starts until the fresh stream is live. */
4304
+ private rotating;
4305
+ /** Lines emitted while `rotating`; flushed into the new generation. */
4306
+ private rotateQueue;
2928
4307
  constructor(cfg?: LoggingConfig);
2929
4308
  info(message: string, meta?: Record<string, unknown> | Error | object): void;
2930
4309
  warn(message: string, meta?: Record<string, unknown> | Error | object): void;
@@ -2944,7 +4323,22 @@ declare class ConfigurableLogger implements Logger {
2944
4323
  private writeConsole;
2945
4324
  /** Append one line to the file sink; a failure disables the sink (swallowed). */
2946
4325
  private writeFile;
2947
- /** Lazily open the append-only file stream; disable the sink on any error. */
4326
+ /** Write ONE prepared line to the live generation, rotating once it is full. */
4327
+ private emitLine;
4328
+ /**
4329
+ * Close the full generation, shift `<file>.N-1` → `<file>.N` (oldest dropped),
4330
+ * then reopen. Renames run in the `end()` callback so nothing still buffered in
4331
+ * the stream lands in the wrong generation. Any failure disables the sink
4332
+ * rather than throwing — the console sink is unaffected either way.
4333
+ */
4334
+ private rotate;
4335
+ /** `<file>.N` unlinked, `<file>.k` → `<file>.k+1`, `<file>` → `<file>.1`. */
4336
+ private shiftGenerations;
4337
+ /**
4338
+ * Lazily open the append-only file stream; disable the sink on any error. The
4339
+ * byte counter is seeded from the file already on disk so a restart cannot
4340
+ * reset an almost-full generation back to zero and blow past the cap.
4341
+ */
2948
4342
  private getFileStream;
2949
4343
  private consoleFn;
2950
4344
  /** `{ ts, level, msg, ...meta }` (+ `error` when present) as a single line. */
@@ -3034,4 +4428,4 @@ declare function mapCcrToOmnicross(ccr: CcrConfig): {
3034
4428
  notes: string[];
3035
4429
  };
3036
4430
 
3037
- 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 };
4431
+ 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 };