@voltro/plugin-storage 0.85.0 → 0.87.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 (52) hide show
  1. package/CHANGELOG.md +5987 -0
  2. package/README.md +77 -0
  3. package/THIRD-PARTY-NOTICES.md +860 -249
  4. package/dist/access.js +47 -0
  5. package/dist/assetRef.js +13 -0
  6. package/dist/avatar.js +39 -0
  7. package/dist/byteSecurity.d.ts +3 -0
  8. package/dist/byteSecurity.js +7 -0
  9. package/dist/connection.js +99 -0
  10. package/dist/constraints.js +93 -0
  11. package/dist/custody.js +132 -0
  12. package/dist/derivatives.js +30 -0
  13. package/dist/env.js +162 -0
  14. package/dist/grant.js +132 -0
  15. package/dist/index.d.ts +525 -203
  16. package/dist/index.js +765 -3196
  17. package/dist/ingest.js +87 -0
  18. package/dist/ingestPolicy.js +1 -0
  19. package/dist/metadataError.js +19 -0
  20. package/dist/objectAbandon.js +16 -0
  21. package/dist/objectLifecycle.js +243 -0
  22. package/dist/objectMetadata.js +308 -0
  23. package/dist/objectWriter.js +87 -0
  24. package/dist/pathDurability.js +16 -0
  25. package/dist/physicalReferences.js +54 -0
  26. package/dist/providers.js +638 -0
  27. package/dist/referenceCatalogue.js +31 -0
  28. package/dist/renditionProof.js +82 -0
  29. package/dist/retainedCapture.js +391 -0
  30. package/dist/retainedObjects.js +190 -0
  31. package/dist/rpc.d.ts +3 -15
  32. package/dist/rpc.js +3 -1
  33. package/dist/scanners.js +46 -0
  34. package/dist/server.d.ts +153 -0
  35. package/dist/server.js +4 -0
  36. package/dist/service.js +1418 -0
  37. package/dist/sourceIdentity.js +14 -0
  38. package/dist/sourcePin.js +35 -0
  39. package/dist/storageNamespaces.js +80 -0
  40. package/dist/storageNumbers.js +13 -0
  41. package/dist/storageObjectNamespaces.js +243 -0
  42. package/dist/storageReferenceInstances.js +52 -0
  43. package/dist/storageRequest.js +10 -0
  44. package/dist/storageRouting.js +46 -0
  45. package/dist/streamBridge.js +33 -0
  46. package/dist/table.js +54 -0
  47. package/dist/transcodeVideo.js +91 -0
  48. package/dist/transform.js +94 -0
  49. package/dist/transformCache.js +31 -0
  50. package/dist/types.d.ts +116 -134
  51. package/dist/types.js +14 -14
  52. package/package.json +17 -6
package/dist/index.d.ts CHANGED
@@ -1,16 +1,32 @@
1
+ import { ByteStream } from '@voltro/kv/storage';
1
2
  import { ColumnBuilder } from '@voltro/database';
2
3
  import { ColumnType } from '@voltro/database';
4
+ import { ConnectionRef } from '@voltro/kv/connection';
5
+ import { ConnectionRegistryShape } from '@voltro/kv/connection';
3
6
  import { Context } from 'effect';
4
7
  import { DataStore } from '@voltro/database';
5
8
  import { Effect } from 'effect';
6
9
  import { FieldDefinitions } from '@voltro/database';
10
+ import { isStorageKey } from '@voltro/kv/storage';
7
11
  import { Layer } from 'effect';
8
12
  import { mixin } from '@voltro/database';
13
+ import { ObjectMeta } from '@voltro/kv/storage';
9
14
  import { PluginEgressDeclaration } from '@voltro/protocol';
10
15
  import { Readable } from 'node:stream';
11
16
  import { Schema } from 'effect';
12
17
  import { StorageAccessPolicy } from '@voltro/protocol';
13
18
  import { StorageAccessRule } from '@voltro/protocol';
19
+ import { StorageAddress } from '@voltro/kv/storage';
20
+ import { storageAddressIdentity } from '@voltro/kv/storage';
21
+ import { StorageConnectionConnector } from '@voltro/kv/storage';
22
+ import { StorageDestination } from '@voltro/kv/storage';
23
+ import { StorageDestinationRouter } from '@voltro/kv/storage';
24
+ import { StorageError } from '@voltro/kv/storage';
25
+ import { StorageObjectLocation } from '@voltro/kv/storage';
26
+ import { StorageProvider } from '@voltro/kv/storage';
27
+ import { StorageProviderName } from '@voltro/kv/storage';
28
+ import { StoredObject } from '@voltro/kv/storage';
29
+ import { StoredStream } from '@voltro/kv/storage';
14
30
  import { Stream } from 'effect';
15
31
  import { Subject } from '@voltro/protocol';
16
32
  import { Table } from '@voltro/database';
@@ -35,12 +51,24 @@ export declare type AccessPolicy = StorageAccessPolicy;
35
51
  */
36
52
  export declare type AccessRule = StorageAccessRule;
37
53
 
38
- /**
39
- * The provider the APP configured, falling back to the env-derived default when no
40
- * `storagePlugin(...)` is installed. Use this — not `resolveStorageProvider({})` —
41
- * anywhere outside the plugin that needs "the storage this app actually uses".
42
- */
43
- export declare const appStorageProvider: () => StorageProvider;
54
+ /** The app Storage plugin's bound service and durable object metadata. */
55
+ export declare interface AppStorageObjects {
56
+ readonly service: StorageServiceShape;
57
+ readonly metadata: StorageObjectMetadata;
58
+ /** The destination new objects land in. */
59
+ readonly bucket: string;
60
+ }
61
+
62
+ /** Borrow the bound service and object metadata of the app's one Storage plugin, for server code that runs
63
+ * outside the request layer (a Rooms Host, a geometry source). Read it after `bindDataStore`. Refuses with no
64
+ * or several Storage installs, and before binding or with app-supplied ref/grant stores (no metadata boundary). */
65
+ export declare const appStorageObjects: (plugins: ReadonlyArray<VoltroPlugin>) => AppStorageObjects;
66
+
67
+ /** Borrow the app's configured destination. Absence is an error, never a fresh memory store. */
68
+ export declare const appStorageProvider: (plugins: ReadonlyArray<VoltroPlugin>) => StorageProvider;
69
+
70
+ /** Resolve persisted ownership across this app, independently of new defaults. */
71
+ export declare const appStorageRouter: (plugins: ReadonlyArray<VoltroPlugin>) => StorageDestinationRouter;
44
72
 
45
73
  /** A column binding an entity to a stored asset (a `_voltro_storage_refs` id). */
46
74
  export declare function assetRef(options: AssetRefOptions & {
@@ -58,6 +86,20 @@ export declare interface AssetRefOptions {
58
86
  readonly nullable?: boolean;
59
87
  }
60
88
 
89
+ /** The caller owns the after-schema transaction, with serving replicas stopped.
90
+ * Assignments preserve reference IDs, keys, grants, raw put identities, claims
91
+ * and quota. No provider I/O or nested transaction occurs. Returned cleanup
92
+ * targets run through their matching service after boot. */
93
+ export declare const assignStorageObjectNamespacesInTransaction: (store: DataStore, scopes: readonly StorageReferenceScope[], assignments: readonly StorageObjectNamespaceAssignment[]) => Promise<StorageObjectNamespaceMigrationResult>;
94
+
95
+ /** Run in an after-schema migration with serving replicas stopped. The mapping
96
+ * comes from the operator, never from provider failures or the default name.
97
+ * The supplied store belongs to the caller’s transaction. No nested transaction
98
+ * is opened. Only ownership columns change: IDs, bytes, claims, quota and replay stay put. */
99
+ export declare const assignStorageReferenceInstancesInTransaction: (store: DataStore, instances: readonly string[], assignments: readonly StorageReferenceInstanceAssignment[]) => Promise<{
100
+ readonly assigned: number;
101
+ }>;
102
+
61
103
  export declare const azureProvider: (options: AzureProviderOptions) => StorageProvider;
62
104
 
63
105
  export declare interface AzureProviderOptions {
@@ -82,14 +124,7 @@ export declare const buildStorageService: (options: StorageServiceOptions) => St
82
124
  * stream. `subarray` keeps the chunks as views (no copy). */
83
125
  export declare const bytesToStream: (bytes: Uint8Array, chunkSize?: number) => ByteStream;
84
126
 
85
- /**
86
- * A lazy stream of bytes — the streaming counterpart to {@link StoredObject}'s
87
- * eager `bytes`. Backpressured: chunks are pulled on demand, so a multi-GB blob
88
- * never fully materialises in memory. The error channel is {@link StorageError}
89
- * so a stream handed to `putStream` carries the same typed failure the rest of
90
- * the transport does (callers map their source errors into it).
91
- */
92
- export declare type ByteStream = Stream.Stream<Uint8Array, StorageError>;
127
+ export { ByteStream }
93
128
 
94
129
  /** Check an upload against the limits. Returns `null` when ok, else a
95
130
  * `{ reason, detail }` describing the violation. */
@@ -127,24 +162,13 @@ export declare const collectStream: (stream: ByteStream) => Effect.Effect<Uint8A
127
162
  /** Concatenate byte chunks into one contiguous Uint8Array. */
128
163
  export declare const concatBytes: (chunks: ReadonlyArray<Uint8Array>) => Uint8Array;
129
164
 
130
- /**
131
- * The app's provider, or `undefined` when NOBODY named one.
132
- *
133
- * `resolveProvider({})` cannot answer this: its final `default:` arm returns an
134
- * in-process `memoryProvider`, so it is total by construction and every caller
135
- * checking `if (!provider)` is checking a condition that cannot hold. Three of
136
- * them existed — the `--assets` refusals in `voltro data backup` / `export` /
137
- * `restore`, whose whole job is to refuse a flag the app cannot honour. They
138
- * were dead, and what they let through is worse than an unchecked flag: with no
139
- * storage configured, `backup --assets` captured from a FRESH memory provider,
140
- * found nothing in it, and wrote an artefact stamped `assets: { count: 0 }` —
141
- * a rollback story that says the blobs are in there.
142
- *
143
- * "Configured" means one of the two things a person actually did: installed
144
- * `storagePlugin(...)` (which registers here at construction), or set
145
- * `STORAGE_PROVIDER`. A memory provider nobody asked for is not a decision.
146
- */
147
- export declare const configuredStorageProvider: () => StorageProvider | undefined;
165
+ /** Select only transports contributed by this app's plugin instances.
166
+ * Multiple destinations require explicit asset routing, never last-writer wins.
167
+ * An implicit in-memory fallback is never reported as configured storage. */
168
+ export declare const configuredStorageProvider: (plugins: ReadonlyArray<VoltroPlugin>) => StorageProvider | undefined;
169
+
170
+ /** The CLI injects this server adapter into the leaf registry. No plugin imports enter KV. */
171
+ export declare const connectStorage: StorageConnectionConnector;
148
172
 
149
173
  /** `image/*` matches `image/png`; exact otherwise. */
150
174
  export declare const contentTypeAllowed: (contentType: string, allowed: ReadonlyArray<string> | undefined) => boolean;
@@ -159,10 +183,13 @@ export declare const databaseProvider: () => StorageProvider & {
159
183
  bindDataStore: (store: unknown) => void;
160
184
  };
161
185
 
162
- export declare const dataStoreGrantStore: (store: DataStore) => GrantStore;
186
+ /** Dynamic grants resolve through a currently visible reference. The concrete
187
+ * store retains its namespace through cleanup; grant rows alone never authorize
188
+ * bytes in a newly rebound destination. */
189
+ export declare const dataStoreGrantStore: (store: DataStore, configuredView: StorageReferenceView) => GrantStore;
163
190
 
164
191
  /** Ref store backed by the app's `DataStore` (`_voltro_storage_refs`). */
165
- export declare const dataStoreRefStore: (store: DataStore) => RefStore;
192
+ export declare const dataStoreRefStore: (store: DataStore, configuredScope: StorageReferenceView) => RefStore;
166
193
 
167
194
  /** Decode a `data:<mime>;base64,<data>` URI to bytes + content type. */
168
195
  export declare const decodeDataUri: (dataUri: string) => {
@@ -189,6 +216,23 @@ export declare const editImage: (bytes: Uint8Array, contentType: string, edit: I
189
216
  * chunks, terminated by a zero-length chunk. Exposed for testing. */
190
217
  export declare const encodeInstream: (bytes: Uint8Array, chunkSize?: number) => Buffer;
191
218
 
219
+ /** Boot-time initialization after normal schema reconciliation. This identity
220
+ * describes the persisted reference catalogue, not its provider bytes or host. */
221
+ export declare const ensureStorageReferenceCatalogue: (store: DataStore) => Promise<void>;
222
+
223
+ /**
224
+ * The identity a filesystem volume ALREADY carries — never a new one.
225
+ *
226
+ * `filesystemObjectNamespace` mints the marker on an empty root, which is right
227
+ * for a serving process and wrong for a migration: a migrate job started
228
+ * without the volume mounted read an empty directory, minted an identity no
229
+ * serving replica has, and an assignment to it exited 0. A metadata upgrade
230
+ * resolves the namespace through this function, so a missing volume refuses.
231
+ */
232
+ export declare const existingFilesystemObjectNamespace: (root: string) => string;
233
+
234
+ export declare const fetchIngestBody: (url: string, configuredMaxBytes?: number, policy?: IngestPolicy) => Effect.Effect<StoredObject, StorageError | StorageRejected>;
235
+
192
236
  export declare interface FfmpegRenditionSpec {
193
237
  /** Derivative class stored on the rendition ref — `'rendition'` / `'poster'`. */
194
238
  readonly kind: string;
@@ -291,7 +335,7 @@ export declare interface ImageEdit {
291
335
  readonly crop?: Region;
292
336
  }
293
337
 
294
- /** Where `ingestUrl` may fetch from — see `ssrfReason` in `@voltro/runtime`. */
338
+ /** Where `ingestUrl` may fetch from — see `ssrfReason` in `@voltro/protocol`. */
295
339
  export declare interface IngestPolicy {
296
340
  /** Hosts (or `host:port`, or `*.suffix`) reachable although internal — a
297
341
  * deliberate internal source, or a local test server. Everything else that
@@ -299,8 +343,13 @@ export declare interface IngestPolicy {
299
343
  readonly allowHosts?: ReadonlyArray<string>;
300
344
  /** Redirect hops followed, each checked again. Default 5. */
301
345
  readonly maxRedirects?: number;
346
+ /** Refuse HTTP at the initial source and every redirect before fetching.
347
+ * Host allowlisting does not bypass this transport requirement. Default false. */
348
+ readonly requireHttps?: boolean;
302
349
  }
303
350
 
351
+ export { isStorageKey }
352
+
304
353
  /**
305
354
  * Filter + page a `StorageService.listRefs` / media-library query. Every
306
355
  * filter is optional and ANDed; omit them all to page the whole (tenant-scoped)
@@ -336,63 +385,117 @@ export declare interface ListRefsResult {
336
385
  readonly nextOffset: number | null;
337
386
  }
338
387
 
339
- /** DataStore-backed metadata boundary, intentionally no provider access. The
340
- * service must use reserve -> provider.put -> confirm and retain failed intents. */
341
- export declare const makeStorageObjectMetadata: (store: DataStore) => {
342
- /** The put intent a reference was written under, if it was written through an operation at all. */
388
+ /** Each call captures its request tenant; no request changes the selected cell. */
389
+ export declare const makeStorageDestinationRouter: (binding: StorageDestinationBinding) => StorageDestinationRouter;
390
+
391
+ /** Resolve every operation to one concrete physical namespace. Metadata-only
392
+ * discovery stays instance scoped; the persisted intent is validated against
393
+ * the retained destination before it can authorize reads, claims or deletion. */
394
+ export declare const makeStorageObjectMetadata: (store: DataStore, configuredView: StorageReferenceView, admit?: StorageObjectAdmission) => {
395
+ scopeFor: (input: {
396
+ readonly bucket: string;
397
+ readonly tenantId: string | null;
398
+ } | {
399
+ readonly refId: string;
400
+ }) => Promise<StorageReferenceScope>;
343
401
  intent: (refId: string) => Promise<{
344
402
  identity: {
345
403
  readonly tenantId: string | null;
346
404
  readonly bucket: string;
405
+ readonly objectNamespace: string;
406
+ readonly fingerprint: string;
407
+ readonly checksum: string;
408
+ readonly storageInstance: string;
347
409
  readonly operationId: string;
410
+ readonly logicalKey: string;
411
+ readonly retainedSource?: {
412
+ readonly refId: string;
413
+ readonly digest: string;
414
+ };
415
+ };
416
+ ref: StorageRef;
417
+ } | undefined>;
418
+ operationStatus: (input: {
419
+ readonly bucket: string;
420
+ readonly tenantId: string | null;
421
+ readonly operationId: string;
422
+ }) => Promise<{
423
+ observation: StorageOperationState;
424
+ scope: StorageReferenceScope;
425
+ }>;
426
+ lookup: (input: Omit<StorageObjectOperationScope, keyof StorageReferenceScope>, claim: string) => Promise<{
427
+ state: "ready" | "reserved" | "deleting" | "deleted";
428
+ ref: StorageRef;
429
+ held: boolean;
430
+ } | undefined>;
431
+ operation: (key: StorageObjectOperationKey) => Promise<{
432
+ state: "ready" | "reserved" | "deleting" | "deleted";
433
+ identity: {
434
+ readonly tenantId: string | null;
435
+ readonly bucket: string;
436
+ readonly objectNamespace: string;
348
437
  readonly fingerprint: string;
349
438
  readonly checksum: string;
439
+ readonly storageInstance: string;
440
+ readonly operationId: string;
350
441
  readonly logicalKey: string;
442
+ readonly retainedSource?: {
443
+ readonly refId: string;
444
+ readonly digest: string;
445
+ };
351
446
  };
352
447
  ref: StorageRef;
353
448
  } | undefined>;
354
- /** `claim` is registered only where the caller receives the ref: here when
355
- * the operation is already ready (`joined`), else on `confirm`. A claimant
356
- * whose provider write fails therefore holds nothing. */
357
449
  reserve: (identity: StorageObjectIdentity, proposed: StorageRef, quota?: StorageQuota, claim?: string) => Promise<{
358
450
  ref: StorageRef;
359
451
  ready: boolean;
360
452
  outcome: PutOutcome;
361
453
  }>;
362
454
  confirm: (identity: StorageObjectIdentity, claim?: string) => Promise<StorageRef>;
363
- /** With a `claim`, releases that claim and deletes the object only when no
364
- * other claimant holds it; `released` alone (no `operation`) means another
365
- * claimant still does. Without one, the whole object goes, claims included. */
366
455
  beginDelete: (refId: string, tenantId?: string | null, claim?: string) => Promise<{
367
456
  released: true;
368
457
  identity: {
369
458
  readonly tenantId: string | null;
370
459
  readonly bucket: string;
371
- readonly operationId: string;
460
+ readonly objectNamespace: string;
372
461
  readonly fingerprint: string;
373
462
  readonly checksum: string;
463
+ readonly storageInstance: string;
464
+ readonly operationId: string;
374
465
  readonly logicalKey: string;
466
+ readonly retainedSource?: {
467
+ readonly refId: string;
468
+ readonly digest: string;
469
+ };
375
470
  };
376
471
  operation?: never;
377
472
  } | {
378
473
  released: true;
379
474
  operation: {
380
475
  readonly id: string;
476
+ readonly storageInstance: string;
477
+ readonly objectNamespace: string;
381
478
  readonly objectId: string;
382
479
  readonly fingerprint: string;
383
480
  readonly generation: string;
384
481
  readonly key: string;
385
482
  readonly refId: string;
386
- readonly state: "ready" | "reserved" | "deleted" | "deleting";
483
+ readonly state: "ready" | "reserved" | "deleting" | "deleted";
387
484
  readonly deleteKey: string | null;
388
485
  };
389
486
  identity: {
390
487
  readonly tenantId: string | null;
391
488
  readonly bucket: string;
392
- readonly operationId: string;
489
+ readonly objectNamespace: string;
393
490
  readonly fingerprint: string;
394
491
  readonly checksum: string;
492
+ readonly storageInstance: string;
493
+ readonly operationId: string;
395
494
  readonly logicalKey: string;
495
+ readonly retainedSource?: {
496
+ readonly refId: string;
497
+ readonly digest: string;
498
+ };
396
499
  };
397
500
  }>;
398
501
  confirmDelete: (identity: StorageObjectIdentity) => Promise<boolean>;
@@ -414,11 +517,7 @@ export declare interface MultipartTicketPayload extends Omit<UploadTicketPayload
414
517
  readonly size: number;
415
518
  }
416
519
 
417
- /** Result of a provider `put` / `head`. */
418
- export declare interface ObjectMeta {
419
- readonly size: number;
420
- readonly etag?: string;
421
- }
520
+ export { ObjectMeta }
422
521
 
423
522
  /** Parse a clamd INSTREAM reply: `stream: OK` → clean; `stream: <T> FOUND`
424
523
  * → infected; anything else → throws. Exposed for testing. */
@@ -455,6 +554,8 @@ export declare const putAvatar: (storage: StorageServiceShape, args: {
455
554
 
456
555
  /** Input to `StorageService.put`. */
457
556
  export declare interface PutInput {
557
+ /** Server-owned logical destination; omitted new writes use this plugin's selection. */
558
+ readonly destination?: string;
458
559
  readonly bytes: Uint8Array;
459
560
  readonly contentType: string;
460
561
  /** Active org id — scopes the key prefix + the ref row. */
@@ -546,6 +647,13 @@ export declare interface RefStatistics {
546
647
  }
547
648
 
548
649
  export declare interface RefStore {
650
+ /** Complete metadata-only ownership observation; no pagination or recent limit. */
651
+ readonly listObjectReferences: () => Effect.Effect<ReadonlyArray<StorageObjectReference>, StorageError>;
652
+ /** Stable identity of this actual reference view. Shared catalogues expose
653
+ * the same identity; independent/process-local stores must expose their own. */
654
+ readonly catalogueId: Effect.Effect<string, StorageError>;
655
+ /** Durable reservations retain bytes before a reference is confirmed. */
656
+ readonly hasObjectReservation?: (namespace: string, key: string) => Effect.Effect<boolean, StorageError>;
549
657
  /** Aggregate the complete reference index without a recent-object limit. */
550
658
  readonly statistics: () => Effect.Effect<RefStatistics, StorageError>;
551
659
  readonly insert: (ref: StorageRef) => Effect.Effect<void, StorageError>;
@@ -555,10 +663,8 @@ export declare interface RefStore {
555
663
  * same id refund the tenant counter exactly once. */
556
664
  readonly delete: (id: string) => Effect.Effect<boolean, StorageError>;
557
665
  readonly listRecent: (limit: number) => Effect.Effect<ReadonlyArray<StorageRef>, StorageError>;
558
- /** How many refs (in this bucket) point at this provider key — drives
559
- * refcount-before-delete so a dedup'd object isn't yanked from under a
560
- * sibling ref. */
561
- readonly countByKey: (bucket: string, key: string) => Effect.Effect<number, StorageError>;
666
+ /** Logical address count; physical cleanup also observes other destinations. */
667
+ readonly countByAddress: (address: StorageAddress) => Effect.Effect<number, StorageError>;
562
668
  /** Total bytes + object count for a tenant — LIVE sum over the ref rows
563
669
  * (reporting / dashboards). The quota GATE does not read this — it rides
564
670
  * the atomic `consumeUsage` counter, which a read-then-compare here could
@@ -595,7 +701,7 @@ export declare interface RefStore {
595
701
  readonly setVisibility?: ((id: string, visibility: StorageVisibility) => Effect.Effect<StorageRef | null, StorageError>) | undefined;
596
702
  /** How many PUBLIC refs point at this provider key — whether the object must
597
703
  * stay publicly readable. */
598
- readonly countPublicByKey?: ((bucket: string, key: string) => Effect.Effect<number, StorageError>) | undefined;
704
+ readonly countPublicByAddress?: ((address: StorageAddress) => Effect.Effect<number, StorageError>) | undefined;
599
705
  }
600
706
 
601
707
  /** A region of an image in pixels. */
@@ -609,6 +715,8 @@ declare interface Region {
609
715
  /** One derived output a `Transcoder` produces from a source asset. Each becomes
610
716
  * a normal storage ref linked back to the original via `derivedFrom`. */
611
717
  export declare interface Rendition {
718
+ /** Optional structured producer attestation; unproven outputs keep it absent. */
719
+ readonly derivation?: RenditionDerivation;
612
720
  readonly bytes: Uint8Array;
613
721
  readonly contentType: string;
614
722
  /** Derivative class — e.g. `'rendition'` (a playable variant) or `'poster'`. */
@@ -617,6 +725,49 @@ export declare interface Rendition {
617
725
  readonly label?: string;
618
726
  }
619
727
 
728
+ /** Producer attestation. Digests bind a normalized recipe and the observed
729
+ * engine build; the consumer still admits allowed recipes, engines and rights. */
730
+ export declare interface RenditionDerivation {
731
+ readonly recipe: {
732
+ readonly format: string;
733
+ readonly digest: string;
734
+ };
735
+ readonly engine: {
736
+ readonly name: string;
737
+ readonly version: string;
738
+ readonly digest: string;
739
+ };
740
+ }
741
+
742
+ /** Storage-verified input/output identity around a producer attestation.
743
+ * Not an approval, rights grant or proof of complete external dependencies. */
744
+ export declare interface RenditionProof extends RenditionDerivation {
745
+ readonly version: 1;
746
+ readonly source: {
747
+ readonly id: string;
748
+ readonly metadataDigest: string;
749
+ readonly checksum: string;
750
+ readonly size: number;
751
+ readonly contentType: string;
752
+ };
753
+ readonly output: {
754
+ readonly checksum: string;
755
+ readonly size: number;
756
+ readonly contentType: string;
757
+ };
758
+ readonly digest: string;
759
+ }
760
+
761
+ /**
762
+ * Nullable additions deliberately fail closed until the explicit upgrade.
763
+ *
764
+ * The refusal names every table still holding unassigned rows and how many,
765
+ * because the commonest way to meet it AFTER a completed assignment is a
766
+ * reference a previous-version replica wrote while the assignment ran — and a
767
+ * recorded file migration does not run again by itself.
768
+ */
769
+ export declare const requireStorageObjectNamespaces: (store: DataStore) => Promise<void>;
770
+
620
771
  /** A `ListRefsInput` after the service normalizes its defaults / clamps — what
621
772
  * a `RefStore.listRefs` impl receives. `limit` is already the `+1` probe. */
622
773
  export declare interface ResolvedListQuery {
@@ -632,11 +783,7 @@ export declare interface ResolvedListQuery {
632
783
 
633
784
  export declare const resolveStorageProvider: (options: StoragePluginOptions) => StorageProvider;
634
785
 
635
- /**
636
- * The signing secret. A dedicated `VOLTRO_STORAGE_SECRET` is preferred so
637
- * grant tokens and session cookies can be rotated independently; absent
638
- * that we fall back to the session secret so it works out of the box.
639
- */
786
+ /** The secret new storage tokens are signed with — the first of {@link resolveStorageSecrets}. */
640
787
  export declare const resolveStorageSecret: () => string;
641
788
 
642
789
  /** What a resumable ticket authorizes: one chunked upload session, until `exp`. */
@@ -647,6 +794,63 @@ export declare interface ResumableTicketPayload extends Omit<UploadTicketPayload
647
794
  readonly size: number;
648
795
  }
649
796
 
797
+ export declare interface RetainedCaptureKey {
798
+ readonly id: string;
799
+ readonly instance: string;
800
+ readonly objectNamespace: string;
801
+ readonly tenantId: string | null;
802
+ readonly bucket: string;
803
+ /** Binds the caller's immutable publication/configuration admission. */
804
+ readonly contextDigest: string;
805
+ /** Different configured source; absent for same-instance custody. */
806
+ readonly source?: {
807
+ readonly instance: string;
808
+ readonly objectNamespace: string;
809
+ readonly bucket: string;
810
+ };
811
+ }
812
+
813
+ export declare interface RetainedCapturePin extends RetainedCaptureKey {
814
+ readonly count: number;
815
+ readonly digest: string;
816
+ }
817
+
818
+ export declare const retainedCaptureTables: readonly [ Table<"_voltro_storage_retained_captures", FieldDefinitions<{
819
+ readonly id: ColumnBuilder<string, "text", boolean>;
820
+ readonly captureId: ColumnBuilder<string, "text", boolean>;
821
+ readonly instance: ColumnBuilder<string, "text", boolean>;
822
+ readonly tenantId: ColumnBuilder<string | null, "text", boolean>;
823
+ readonly bucket: ColumnBuilder<string, "text", boolean>;
824
+ readonly contextDigest: ColumnBuilder<string, "text", boolean>;
825
+ readonly state: ColumnBuilder<"abandoned" | "complete" | "capturing", "text", boolean>;
826
+ readonly nonce: ColumnBuilder<string, "text", boolean>;
827
+ readonly config: ColumnBuilder<unknown, "json", boolean>;
828
+ readonly count: ColumnBuilder<number, "integer", boolean>;
829
+ readonly digest: ColumnBuilder<string | null, "text", boolean>;
830
+ readonly cleanupOrdinal: ColumnBuilder<number, "integer", true>;
831
+ }>, false, never>, Table<"_voltro_storage_retained_capture_items", FieldDefinitions<{
832
+ readonly id: ColumnBuilder<string, "text", boolean>;
833
+ readonly captureId: ColumnBuilder<string, "text", boolean>;
834
+ readonly ordinal: ColumnBuilder<number, "integer", boolean>;
835
+ readonly sourceId: ColumnBuilder<string, "text", boolean>;
836
+ readonly sourceDigest: ColumnBuilder<string, "text", boolean>;
837
+ readonly operationId: ColumnBuilder<string, "text", boolean>;
838
+ readonly pin: ColumnBuilder<unknown, "json", boolean>;
839
+ }>, false, "storage_retained_capture_page">];
840
+
841
+ export declare interface RetainedStorageObject {
842
+ readonly version: 1;
843
+ readonly tenantId: string | null;
844
+ readonly bucket: string;
845
+ readonly operationId: string;
846
+ readonly sourceId: string;
847
+ readonly sourceDigest: string;
848
+ readonly refId: string;
849
+ readonly checksum: string;
850
+ readonly size: number;
851
+ readonly contentType: string;
852
+ }
853
+
650
854
  export declare const s3Provider: (options: S3ProviderOptions) => StorageProvider;
651
855
 
652
856
  export declare interface S3ProviderOptions {
@@ -668,8 +872,7 @@ export declare interface S3ProviderOptions {
668
872
  readonly publicAcl?: boolean;
669
873
  }
670
874
 
671
- /** Register the app's storage provider (or clear with `undefined`). */
672
- export declare const setActiveStorageProvider: (provider: StorageProvider | undefined) => void;
875
+ export declare type ScopedStorageObjectIdentity = StorageObjectIdentity & StorageReferenceScope;
673
876
 
674
877
  /** Sign a finalize token valid for `ttlSeconds`. */
675
878
  export declare const signFinalizeTicket: (fields: Omit<FinalizeTicketPayload, "exp" | "version">, ttlSeconds: number, secret?: string) => string;
@@ -694,6 +897,8 @@ export declare const STORAGE_BLOBS_TABLE = "_voltro_storage_blobs";
694
897
 
695
898
  export declare const STORAGE_GRANTS_TABLE = "_voltro_storage_grants";
696
899
 
900
+ export declare const STORAGE_REFERENCE_CATALOGUE_TABLE = "_voltro_storage_reference_catalogue";
901
+
697
902
  export declare const STORAGE_REFS_TABLE = "_voltro_storage_refs";
698
903
 
699
904
  export declare const STORAGE_USAGE_TABLE = "_voltro_storage_usage";
@@ -711,6 +916,88 @@ declare const StorageAccessDenied_base: Schema.Class<StorageAccessDenied, Schema
711
916
  readonly reason: Schema.String;
712
917
  }>, YieldableError>;
713
918
 
919
+ export { StorageAddress }
920
+
921
+ export { storageAddressIdentity }
922
+
923
+ export declare const storageByteSecurityHeaders: (contentType: string) => Readonly<Record<string, string>>;
924
+
925
+ /** Private-byte custody for trusted server coordinators. storagePlugin supplies
926
+ * this service only for its exact instance. Pins confer no end-user authority. */
927
+ export declare class StorageCustody extends StorageCustody_base {
928
+ /** Reconstruct a transfer from configured services on each worker. Both must
929
+ * use the same application database; only the destination stores custody. */
930
+ static withSource(input: {
931
+ readonly source: StorageCustodyShape;
932
+ readonly destination: StorageCustodyShape;
933
+ }): Effect.Effect<StorageCustodyShape, StorageError>;
934
+ static forInstance(selection?: StorageCustodyInstance): Context.Service<StorageCustody, StorageCustodyShape>;
935
+ }
936
+
937
+ declare const StorageCustody_base: Context.ServiceClass<StorageCustody, string, StorageCustodyShape>;
938
+
939
+ /** Uses the same instance selector as storagePlugin. */
940
+ export declare interface StorageCustodyInstance {
941
+ readonly alias?: string;
942
+ readonly name?: string;
943
+ }
944
+
945
+ /** Trusted server selection. Source enumeration runs in the caller's DB TX. */
946
+ export declare interface StorageCustodyRegistration {
947
+ readonly id: string;
948
+ readonly tenantId: string | null;
949
+ readonly contextDigest: string;
950
+ readonly maxBytes: number;
951
+ readonly sources: () => Iterable<string | StorageSourcePin> | AsyncIterable<string | StorageSourcePin>;
952
+ }
953
+
954
+ export declare interface StorageCustodyShape {
955
+ /** Read and lock metadata from this configured source, including rendition
956
+ * provenance. No provider I/O, access grant or recipe/rights approval.
957
+ * Bind the pin to the caller's immutable candidate and contextDigest, then
958
+ * pass it to register in the same application transaction. */
959
+ readonly inspect: (tx: DataStore, input: {
960
+ readonly id: string;
961
+ readonly tenantId: string | null;
962
+ }) => Effect.Effect<StorageSourcePin, StorageError>;
963
+ /** Register stable work in the same transaction as the caller's source pin.
964
+ * The transaction must belong to this plugin's bound application database. */
965
+ readonly register: (tx: DataStore, input: StorageCustodyRegistration) => Effect.Effect<{
966
+ readonly key: RetainedCaptureKey;
967
+ readonly count: number;
968
+ }, StorageError>;
969
+ /** Copy/verify bytes outside a DB transaction; retries reuse registered work. */
970
+ readonly run: (key: RetainedCaptureKey) => Effect.Effect<void, StorageError>;
971
+ /** Seal with the caller's final manifest and durable result in one DB TX. */
972
+ readonly seal: (tx: DataStore, key: RetainedCaptureKey) => Effect.Effect<RetainedCapturePin, StorageError>;
973
+ /** Private pins. Consume through EOF and stage output until digest success.
974
+ * Pass the same application transaction when enumerating a just-sealed pin. */
975
+ readonly items: (pin: RetainedCapturePin, tx?: DataStore) => Stream.Stream<RetainedStorageObject, StorageError>;
976
+ /** Trusted server read of a verified member pin. Not an access grant.
977
+ * Tenant admission must come from the caller; finish byte verification at EOF. */
978
+ readonly read: (capture: RetainedCapturePin, object: RetainedStorageObject, scope: {
979
+ readonly tenantId: string | null;
980
+ readonly maxBytes: number;
981
+ }) => Effect.Effect<{
982
+ readonly stream: ByteStream;
983
+ readonly contentType: string;
984
+ readonly size: number;
985
+ }, StorageError>;
986
+ /** Close admission before cleanup. Repeat cleanup after uncertain/late I/O. */
987
+ readonly abandon: (key: RetainedCaptureKey) => Effect.Effect<void, StorageError>;
988
+ }
989
+
990
+ export { StorageDestination }
991
+
992
+ declare interface StorageDestinationBinding {
993
+ readonly selected: () => string;
994
+ readonly directDestination: string | undefined;
995
+ readonly directProvider: () => StorageProvider;
996
+ readonly registry: () => ConnectionRegistryShape | undefined;
997
+ }
998
+
999
+ export { StorageDestinationRouter }
1000
+
714
1001
  /**
715
1002
  * Where the bytes go, read from the same options and env `resolveProvider`
716
1003
  * reads: an object store is a destination outside the installation; the
@@ -718,20 +1005,7 @@ declare const StorageAccessDenied_base: Schema.Class<StorageAccessDenied, Schema
718
1005
  */
719
1006
  export declare const storageEgressOf: (options: StoragePluginOptions) => ReadonlyArray<PluginEgressDeclaration>;
720
1007
 
721
- /**
722
- * Storage failure. `transient` drives retry — `true` for 429 / 5xx /
723
- * network blips, `false` for 4xx / config / not-found. Surfaced typed so
724
- * handlers can `Effect.catchTag('StorageError', …)`.
725
- */
726
- export declare class StorageError extends StorageError_base {
727
- }
728
-
729
- declare const StorageError_base: Schema.Class<StorageError, Schema.TaggedStruct<"StorageError", {
730
- readonly provider: Schema.String;
731
- readonly message: Schema.String;
732
- readonly transient: Schema.Boolean;
733
- readonly status: Schema.optional<Schema.Number>;
734
- }>, YieldableError>;
1008
+ export { StorageError }
735
1009
 
736
1010
  /** A persisted, explicit share of one object with one principal. */
737
1011
  export declare interface StorageGrant {
@@ -761,8 +1035,11 @@ export declare type StorageGuard = (ctx: {
761
1035
  * and `BASE_NAME` is not exported, so a caller minting a ticket outside the
762
1036
  * plugin had to guess it. Guessing is exactly what a `string` parameter invites.
763
1037
  *
1038
+ * Use this name in the ticket's `instance` field. The ticket also captures
1039
+ * the selected destination, owner, tenant, content type and visibility.
1040
+ * const writeScope = await Effect.runPromise(service.uploadTicketScope({ tenantId, destination }))
764
1041
  * const ticket = signUploadTicket(
765
- * { instance: storageInstanceName(), key, contentType },
1042
+ * { writeScope, destination, instance: storageInstanceName(options), key, ct: contentType, sub: ownerId, tenant: tenantId, vis: 'private' },
766
1043
  * 300,
767
1044
  * )
768
1045
  *
@@ -772,16 +1049,24 @@ export declare type StorageGuard = (ctx: {
772
1049
  */
773
1050
  export declare const storageInstanceName: (options?: StoragePluginOptions) => string;
774
1051
 
775
- export declare interface StorageObjectIdentity {
776
- readonly tenantId: string | null;
777
- readonly bucket: string;
778
- readonly operationId: string;
1052
+ /** Internal database-only admission. Runs under the storage tenant lock, in
1053
+ * the same transaction as reserve/join/confirm. It must lock or write its own
1054
+ * durable owner row; observing a live flag before this transaction is unsafe.
1055
+ * No provider, engine or other external I/O belongs in this callback. */
1056
+ declare type StorageObjectAdmission = (tx: DataStore, identity: StorageObjectIdentity, phase: 'reserve' | 'confirm') => Promise<void>;
1057
+
1058
+ export declare interface StorageObjectIdentity extends StorageObjectOperationKey {
779
1059
  /** Hash of all immutable put inputs, including content and access metadata. */
780
1060
  readonly fingerprint: string;
781
1061
  /** SHA-256 of the actual normalized provider bytes, separate from metadata. */
782
1062
  readonly checksum: string;
783
1063
  /** Already tenant-scoped logical provider key. */
784
1064
  readonly logicalKey: string;
1065
+ /** Private retained-input provenance; not an access rule or CMS policy. */
1066
+ readonly retainedSource?: {
1067
+ readonly refId: string;
1068
+ readonly digest: string;
1069
+ };
785
1070
  }
786
1071
 
787
1072
  /** Internal persistence substrate. Service wiring must commit quota and refs in
@@ -791,11 +1076,14 @@ export declare const storageObjectLifecycleTables: ( Table<"_voltro_storage_oper
791
1076
  readonly nonce: ColumnBuilder<string, "text", boolean>;
792
1077
  }>, false, never> | Table<"_voltro_storage_object_heads", FieldDefinitions<{
793
1078
  readonly id: ColumnBuilder<string, "text", boolean>;
1079
+ readonly objectNamespace: ColumnBuilder<string | null, "text", boolean>;
794
1080
  readonly generation: ColumnBuilder<string, "text", boolean>;
795
1081
  readonly key: ColumnBuilder<string, "text", boolean>;
796
1082
  readonly reservations: ColumnBuilder<string, "bigint", boolean>;
797
1083
  }>, false, never> | Table<"_voltro_storage_operations", FieldDefinitions<{
798
1084
  readonly id: ColumnBuilder<string, "text", boolean>;
1085
+ readonly storageInstance: ColumnBuilder<string | null, "text", boolean>;
1086
+ readonly objectNamespace: ColumnBuilder<string | null, "text", boolean>;
799
1087
  readonly objectId: ColumnBuilder<string, "text", boolean>;
800
1088
  readonly fingerprint: ColumnBuilder<string, "text", boolean>;
801
1089
  readonly generation: ColumnBuilder<string, "text", boolean>;
@@ -809,13 +1097,59 @@ export declare const storageObjectLifecycleTables: ( Table<"_voltro_storage_oper
809
1097
  readonly claim: ColumnBuilder<string, "text", boolean>;
810
1098
  }>, false, never>)[];
811
1099
 
1100
+ export { StorageObjectLocation }
1101
+
812
1102
  export declare type StorageObjectMetadata = ReturnType<typeof makeStorageObjectMetadata>;
813
1103
 
1104
+ /** Credential-independent identity of a physical object-key namespace.
1105
+ * Equal identities mean an equal key addresses the same provider bytes. */
1106
+ export declare const storageObjectNamespace: (parts: readonly string[]) => string;
1107
+
1108
+ export declare interface StorageObjectNamespaceAssignment extends StorageReferenceScope {
1109
+ readonly refId: string;
1110
+ }
1111
+
1112
+ export declare interface StorageObjectNamespaceMigrationResult {
1113
+ readonly assigned: number;
1114
+ readonly pendingDeletes: readonly StorageObjectNamespaceAssignment[];
1115
+ }
1116
+
1117
+ export declare interface StorageObjectOperationKey {
1118
+ readonly tenantId: string | null;
1119
+ readonly bucket: string;
1120
+ readonly operationId: string;
1121
+ }
1122
+
1123
+ export declare interface StorageObjectOperationScope extends StorageReferenceScope {
1124
+ readonly tenantId: string | null;
1125
+ readonly bucket: string;
1126
+ readonly operationId: string;
1127
+ }
1128
+
1129
+ export declare type StorageObjectReference = Pick<StorageRef, 'bucket' | 'tenantId' | 'key' | 'visibility'> & Partial<StorageReferenceScope>;
1130
+
1131
+ /** A durable metadata observation, not a claim on the object or a provider HEAD. */
1132
+ export declare type StorageOperationState = {
1133
+ readonly state: 'absent' | 'reserved' | 'deleting' | 'deleted';
1134
+ } | {
1135
+ readonly state: 'ready';
1136
+ readonly ref: StorageRef;
1137
+ };
1138
+
1139
+ /** Metadata state qualified by the actual selected storage destination. */
1140
+ export declare type StorageOperationStatus = StorageOperationState & {
1141
+ /** Configured instance, actual catalogue, physical namespace and logical bucket. */
1142
+ readonly scope: string;
1143
+ /** Selected logical destination; retain it with the scope before minting a ticket. */
1144
+ readonly destination: string;
1145
+ };
1146
+
814
1147
  export declare const storagePlugin: (options?: StoragePluginOptions) => VoltroPlugin;
815
1148
 
816
1149
  export declare interface StoragePluginOptions {
817
1150
  /** `'s3' | 'minio' | 'filesystem' | 'memory'` (built from env) or a
818
1151
  * `StorageProvider`. Default: `STORAGE_PROVIDER` env, else `'memory'`. */
1152
+ readonly connection?: ConnectionRef<'storage'>;
819
1153
  readonly provider?: StorageProviderName | StorageProvider;
820
1154
  /** Bucket name (s3/minio). Default `S3_BUCKET`/`STORAGE_BUCKET` env, else 'voltro'. */
821
1155
  readonly bucket?: string;
@@ -959,103 +1293,16 @@ export declare interface StoragePluginOptions {
959
1293
  readonly name?: string;
960
1294
  }
961
1295
 
962
- /**
963
- * A backend transport. Keyed by an opaque object key (the service owns key
964
- * derivation + tenant prefixing); knows nothing about refs, tenants, or
965
- * access policy.
966
- */
967
- export declare interface StorageProvider {
968
- /**
969
- * Where a FILESYSTEM provider writes. Absent for every other provider.
970
- *
971
- * It exists so a caller can ask a question this interface otherwise cannot
972
- * answer: does what I write here survive the process? A rollback capture
973
- * written for a pod that then dies is worthless, and `filesystem` is the one
974
- * provider that can be either — a mounted volume or an ephemeral container
975
- * directory. A deployment pointed this out: their check for "storage is
976
- * configured" passed on a `/tmp` path with no volume behind it.
977
- */
978
- readonly root?: string;
979
- readonly name: string;
980
- /** True when `getUrl` returns a real presigned URL (s3/minio). When
981
- * false the service serves bytes via the `/_voltro/storage/:id`
982
- * endpoint instead (filesystem/memory). */
983
- readonly presigns: boolean;
984
- readonly put: (key: string, bytes: Uint8Array, meta: {
985
- readonly contentType: string;
986
- readonly public?: boolean;
987
- }) => Effect.Effect<ObjectMeta, StorageError>;
988
- readonly get: (key: string) => Effect.Effect<StoredObject, StorageError>;
989
- readonly delete: (key: string) => Effect.Effect<void, StorageError>;
990
- /** Presigned GET (s3/minio) for private reads. */
991
- readonly getUrl: (key: string, expiresInSec: number) => Effect.Effect<string, StorageError>;
992
- /** A direct, cacheable URL for a PUBLIC object — virtual-hosted bucket
993
- * URL or a configured CDN base. `null` when the provider can't serve
994
- * publicly without the app (filesystem/memory) → the service falls
995
- * back to the `/_voltro/storage/:id` route. */
996
- readonly publicUrl?: (key: string) => string | null;
997
- /** Make an object publicly readable at the provider, or not (s3/minio with
998
- * per-object ACLs). Absent where the app decides per request (database,
999
- * filesystem, memory) — there a ref's visibility is the whole answer. */
1000
- readonly setObjectPublic?: (key: string, isPublic: boolean) => Effect.Effect<void, StorageError>;
1001
- /** Presigned PUT for direct-to-bucket uploads (s3/minio). Absent on
1002
- * providers that can't presign uploads. */
1003
- readonly getUploadUrl?: (key: string, expiresInSec: number, contentType: string) => Effect.Effect<string, StorageError>;
1004
- readonly createMultipartUpload?: (key: string, contentType: string, opts?: {
1005
- readonly public?: boolean;
1006
- }) => Effect.Effect<{
1007
- readonly uploadId: string;
1008
- }, StorageError>;
1009
- readonly presignUploadPart?: (key: string, uploadId: string, partNumber: number, expiresInSec: number) => Effect.Effect<string, StorageError>;
1010
- readonly completeMultipartUpload?: (key: string, uploadId: string, parts: ReadonlyArray<{
1011
- readonly partNumber: number;
1012
- readonly etag: string;
1013
- }>) => Effect.Effect<{
1014
- readonly etag?: string;
1015
- }, StorageError>;
1016
- readonly abortMultipartUpload?: (key: string, uploadId: string) => Effect.Effect<void, StorageError>;
1017
- readonly head: (key: string) => Effect.Effect<ObjectMeta | null, StorageError>;
1018
- /**
1019
- * Stream an object's bytes in chunks instead of buffering the whole blob
1020
- * ({@link get}). Present on backends with a real streaming read (filesystem,
1021
- * s3, azure). OPTIONAL: when a provider omits it, {@link getObjectStream}
1022
- * transparently falls back to `get` + a chunked in-memory stream — correct
1023
- * for the small-blob backends (memory / database) that have nothing to
1024
- * stream from.
1025
- */
1026
- readonly getStream?: (key: string) => Effect.Effect<StoredStream, StorageError>;
1027
- /**
1028
- * Consume a {@link ByteStream} and store it via the backend's chunked /
1029
- * multipart upload, so a large upload never fully materialises in memory.
1030
- * Present on filesystem (streamed atomic write), s3 (multipart via
1031
- * `@aws-sdk/lib-storage`), azure (`uploadStream`). OPTIONAL: when absent,
1032
- * {@link putObjectStream} buffers the stream and calls `put`. `totalSize`,
1033
- * when the caller knows it (e.g. from an export manifest), lets the backend
1034
- * report an accurate size without a follow-up HEAD.
1035
- */
1036
- readonly putStream?: (key: string, stream: ByteStream, meta: {
1037
- readonly contentType: string;
1038
- readonly public?: boolean;
1039
- readonly totalSize?: number;
1040
- }) => Effect.Effect<ObjectMeta, StorageError>;
1041
- /**
1042
- * Read a byte range `[start, endInclusive]` (HTTP Range / fd offset). Enables
1043
- * seekable + resumable reads. OPTIONAL and best-effort: whole-object resume
1044
- * already works via content-addressing (skip an asset whose hash is present
1045
- * at the target), so this is an optimisation, not a correctness requirement.
1046
- */
1047
- readonly getRange?: (key: string, start: number, endInclusive: number) => Effect.Effect<Uint8Array, StorageError>;
1048
- /** Providers that store bytes via the app's DataStore (e.g. `database`)
1049
- * receive it here once it exists — bound by the plugin at boot. */
1050
- readonly bindDataStore?: (store: unknown) => void;
1051
- }
1296
+ export { StorageProvider }
1052
1297
 
1053
- export declare type StorageProviderName = 's3' | 'minio' | 'azure' | 'filesystem' | 'memory' | 'database';
1298
+ export { StorageProviderName }
1054
1299
 
1055
1300
  export declare const storagePutIntentsTable: Table<"_voltro_storage_put_intents", FieldDefinitions<{
1056
1301
  readonly id: ColumnBuilder<string, "text", boolean>;
1057
1302
  readonly refId: ColumnBuilder<string, "text", boolean>;
1058
- readonly identity: ColumnBuilder<StorageObjectIdentity, "json", boolean>;
1303
+ readonly storageInstance: ColumnBuilder<string | null, "text", boolean>;
1304
+ readonly objectNamespace: ColumnBuilder<string | null, "text", boolean>;
1305
+ readonly identity: ColumnBuilder<ScopedStorageObjectIdentity, "json", boolean>;
1059
1306
  readonly ref: ColumnBuilder<StorageRef, "json", boolean>;
1060
1307
  }>, false, never>;
1061
1308
 
@@ -1103,8 +1350,38 @@ export declare interface StorageRef {
1103
1350
  readonly derivedFrom?: string | null;
1104
1351
  /** Derivative label — e.g. `'rendition'` / `'poster'`; null on an original. */
1105
1352
  readonly kind?: string | null;
1353
+ /** Immutable provenance for an attested transcode; absent on older/unproven refs. */
1354
+ readonly renditionProof?: RenditionProof | null;
1355
+ }
1356
+
1357
+ /** Persisted destination ownership is independent of the current write selection. */
1358
+ export declare const storageRefAddress: (ref: Pick<StorageRef, "bucket" | "tenantId" | "key">) => StorageAddress;
1359
+
1360
+ export declare const storageReferenceCatalogueTable: Table<"_voltro_storage_reference_catalogue", FieldDefinitions<{
1361
+ readonly id: ColumnBuilder<string, "text", boolean>;
1362
+ readonly namespace: ColumnBuilder<string, "text", boolean>;
1363
+ }>, false, never>;
1364
+
1365
+ export declare interface StorageReferenceInstanceAssignment {
1366
+ readonly refId: string;
1367
+ readonly storageInstance: string;
1368
+ }
1369
+
1370
+ /** A plugin instance may retain several tenant-specific physical destinations.
1371
+ * Resolve the concrete namespace at the boundary; never substitute a sentinel
1372
+ * namespace for a tenant-scoped connection. */
1373
+ export declare interface StorageReferenceResolver {
1374
+ readonly storageInstance: string;
1375
+ readonly resolveNamespace: (address: StorageAddress) => Effect.Effect<string, StorageError>;
1376
+ }
1377
+
1378
+ export declare interface StorageReferenceScope {
1379
+ readonly storageInstance: string;
1380
+ readonly objectNamespace: string;
1106
1381
  }
1107
1382
 
1383
+ export declare type StorageReferenceView = StorageReferenceScope | StorageReferenceResolver;
1384
+
1108
1385
  /** An upload was rejected by a constraint (size / content-type / magic-byte
1109
1386
  * mismatch). `reason` is a stable code; `detail` is human-readable. */
1110
1387
  export declare class StorageRejected extends StorageRejected_base {
@@ -1139,6 +1416,9 @@ export declare interface StorageScanResult {
1139
1416
  readonly threat?: string;
1140
1417
  }
1141
1418
 
1419
+ /** A secret, or the set a rotation keeps accepting (the signing one first). */
1420
+ declare type StorageSecrets = string | ReadonlyArray<string>;
1421
+
1142
1422
  export declare class StorageService extends StorageService_base {
1143
1423
  }
1144
1424
 
@@ -1151,6 +1431,12 @@ export declare interface StorageServiceOptions {
1151
1431
  readonly refStore: RefStore;
1152
1432
  readonly grantStore: GrantStore;
1153
1433
  readonly bucket: string;
1434
+ /** New writes select a destination; reads retain SQL destination/tenant ownership. */
1435
+ readonly destinations?: StorageDestinationRouter;
1436
+ /** App-owned resolution of each persisted physical reference owner. */
1437
+ readonly resolveObjectReference?: (reference: StorageObjectReference) => Effect.Effect<StorageProvider, StorageError>;
1438
+ /** Canonical plugin instance; separates new plain keys on a shared provider. */
1439
+ readonly storageInstance?: string;
1154
1440
  /** Static prefix prepended to every key (e.g. an app/env namespace). */
1155
1441
  readonly tenantPrefix?: string;
1156
1442
  /** Transient-failure retries (429 / 5xx / network). Default 3. */
@@ -1203,6 +1489,20 @@ export declare interface StorageServiceOptions {
1203
1489
 
1204
1490
  /** The service handlers consume: `const storage = yield* StorageService`. */
1205
1491
  export declare interface StorageServiceShape {
1492
+ /** Trusted server read of one raw durable put identity in this configured
1493
+ * instance/physical namespace/bucket and the explicitly supplied tenant.
1494
+ * No access-policy check, claim, byte read, retry write or provider HEAD.
1495
+ * Unbound/unsupported/corrupt metadata refuses; it never becomes absence. */
1496
+ readonly operationStatus: (input: {
1497
+ readonly operationId: string;
1498
+ readonly tenantId: string | null;
1499
+ readonly destination?: string;
1500
+ }) => Effect.Effect<StorageOperationStatus, StorageError>;
1501
+ /** Opaque destination binding for manually signed upload-family credentials. */
1502
+ readonly uploadTicketScope: (input?: {
1503
+ readonly tenantId?: string | null;
1504
+ readonly destination?: string;
1505
+ }) => Effect.Effect<string, StorageError>;
1206
1506
  /** Store bytes. The result is the ref plus this call's `outcome`. */
1207
1507
  readonly put: (input: PutInput) => Effect.Effect<StoredRef, StorageError | StorageRejected | StorageScanRejected>;
1208
1508
  /** Fetch an object's bytes by id. Enforces the tenant guard ONLY — it does
@@ -1373,9 +1673,9 @@ export declare interface StorageServiceShape {
1373
1673
  readonly contentType: string;
1374
1674
  readonly tenantId?: string | null;
1375
1675
  readonly expiresInSec?: number;
1376
- }) => Effect.Effect<{
1676
+ }) => Effect.Effect<StorageAddress & {
1677
+ readonly writeScope: string;
1377
1678
  readonly uploadUrl: string;
1378
- readonly key: string;
1379
1679
  readonly expiresInSec: number;
1380
1680
  }, StorageError>;
1381
1681
  /** Promote a blob the client PUT directly to a TEMP `key` into a real ref:
@@ -1384,6 +1684,7 @@ export declare interface StorageServiceShape {
1384
1684
  * scanned + a non-degraded ref — out of band from the direct PUT. On a scan
1385
1685
  * reject the temp bytes are deleted (fail-closed, nothing promoted). */
1386
1686
  readonly finalizeUpload: (input: {
1687
+ readonly destination: string;
1387
1688
  readonly key: string;
1388
1689
  readonly contentType: string;
1389
1690
  readonly tenantId?: string | null;
@@ -1398,6 +1699,7 @@ export declare interface StorageServiceShape {
1398
1699
  /** Store one chunk of a resumable upload as its own durable `_incoming/<id>/
1399
1700
  * <index>` object. Idempotent — re-PUTting the same index overwrites it. */
1400
1701
  readonly putResumableChunk: (input: {
1702
+ readonly destination: string;
1401
1703
  readonly uploadId: string;
1402
1704
  readonly index: number;
1403
1705
  readonly bytes: Uint8Array;
@@ -1407,6 +1709,7 @@ export declare interface StorageServiceShape {
1407
1709
  * 0 and returns the received byte `offset` + the `nextIndex` to send. Lets a
1408
1710
  * reconnecting client resume instead of restarting. */
1409
1711
  readonly resumableStatus: (input: {
1712
+ readonly destination: string;
1410
1713
  readonly uploadId: string;
1411
1714
  readonly tenantId?: string | null;
1412
1715
  }) => Effect.Effect<{
@@ -1417,6 +1720,7 @@ export declare interface StorageServiceShape {
1417
1720
  * concatenate, run the FULL `put()` pipeline, then delete the chunk objects
1418
1721
  * (on success AND on reject — fail-closed). */
1419
1722
  readonly finalizeResumable: (input: {
1723
+ readonly destination: string;
1420
1724
  readonly uploadId: string;
1421
1725
  readonly count: number;
1422
1726
  readonly contentType: string;
@@ -1448,12 +1752,14 @@ export declare interface StorageServiceShape {
1448
1752
  readonly tenantId?: string | null;
1449
1753
  readonly visibility?: StorageVisibility;
1450
1754
  readonly expiresInSec?: number;
1451
- }) => Effect.Effect<{
1755
+ }) => Effect.Effect<StorageAddress & {
1756
+ readonly writeScope: string;
1452
1757
  readonly uploadId: string;
1453
- readonly key: string;
1454
1758
  }, StorageError>;
1455
1759
  /** Presign a PUT for one part (1-based `partNumber`). */
1456
1760
  readonly signMultipartPart: (input: {
1761
+ readonly destination: string;
1762
+ readonly tenantId: string | null;
1457
1763
  readonly key: string;
1458
1764
  readonly uploadId: string;
1459
1765
  readonly partNumber: number;
@@ -1463,6 +1769,7 @@ export declare interface StorageServiceShape {
1463
1769
  * registered straight from it (size via HEAD, etag as checksum). No fetch-back,
1464
1770
  * so NO scan/derivatives — scan multi-GB out of band. */
1465
1771
  readonly completeMultipart: (input: {
1772
+ readonly destination: string;
1466
1773
  readonly key: string;
1467
1774
  readonly uploadId: string;
1468
1775
  readonly parts: ReadonlyArray<{
@@ -1481,20 +1788,36 @@ export declare interface StorageServiceShape {
1481
1788
  }) => Effect.Effect<StorageRef, StorageError | StorageRejected>;
1482
1789
  /** Abort a multipart session (client cancelled) — frees the bucket's parts. */
1483
1790
  readonly abortMultipart: (input: {
1791
+ readonly destination: string;
1792
+ readonly tenantId: string | null;
1484
1793
  readonly key: string;
1485
1794
  readonly uploadId: string;
1486
1795
  }) => Effect.Effect<void, StorageError>;
1487
1796
  }
1488
1797
 
1798
+ /** Trusted build-input metadata, not a delivery DTO or access grant. The
1799
+ * caller persists this alongside its immutable publication/recipe selection. */
1800
+ export declare interface StorageSourcePin {
1801
+ readonly version: 1;
1802
+ readonly instance: string;
1803
+ readonly tenantId: string | null;
1804
+ readonly bucket: string;
1805
+ readonly id: string;
1806
+ readonly sourceDigest: string;
1807
+ readonly checksum: string;
1808
+ readonly size: number;
1809
+ readonly contentType: string;
1810
+ readonly derivedFrom: string | null;
1811
+ readonly kind: string | null;
1812
+ readonly renditionProof: RenditionProof | null;
1813
+ readonly digest: string;
1814
+ }
1815
+
1489
1816
  /** `public` = anyone, served direct from bucket/CDN; `private` = gated by
1490
1817
  * the ref's access policy + grants. Default for a new put is `private`. */
1491
1818
  export declare type StorageVisibility = 'public' | 'private';
1492
1819
 
1493
- /** Bytes + content type as returned by a provider `get`. */
1494
- export declare interface StoredObject {
1495
- readonly bytes: Uint8Array;
1496
- readonly contentType: string;
1497
- }
1820
+ export { StoredObject }
1498
1821
 
1499
1822
  /** What `put` returns: the stored ref, plus what this call did to it. The
1500
1823
  * `outcome` is a fact about the CALL, not the row — it is never persisted and
@@ -1503,13 +1826,7 @@ export declare type StoredRef = StorageRef & {
1503
1826
  readonly outcome: PutOutcome;
1504
1827
  };
1505
1828
 
1506
- /** A streamed read from a provider — the object as a {@link ByteStream} plus
1507
- * its content type and, when the backend reports it up front, its size. */
1508
- export declare interface StoredStream {
1509
- readonly stream: ByteStream;
1510
- readonly contentType: string;
1511
- readonly size?: number;
1512
- }
1829
+ export { StoredStream }
1513
1830
 
1514
1831
  /**
1515
1832
  * A ByteStream → Node `Readable`, for SDK uploaders that consume Node streams
@@ -1531,9 +1848,7 @@ export declare type Transcoder = (input: {
1531
1848
  readonly ref: StorageRef;
1532
1849
  }) => ReadonlyArray<Rendition> | Promise<ReadonlyArray<Rendition>> | Effect.Effect<ReadonlyArray<Rendition>>;
1533
1850
 
1534
- /** Transform with a small FIFO cache keyed by `(refId, params)` so repeated
1535
- * requests for the same variant don't re-encode. */
1536
- export declare const transformCached: (refId: string, bytes: Uint8Array, contentType: string, params: TransformParams) => Promise<Transformed | null>;
1851
+ export declare const transformCached: (addressIdentity: string, bytes: Uint8Array, contentType: string, params: TransformParams) => Promise<Transformed | null>;
1537
1852
 
1538
1853
  export declare interface Transformed {
1539
1854
  readonly bytes: Uint8Array;
@@ -1587,13 +1902,17 @@ export declare interface UploadLimits {
1587
1902
  * owner/tenant/content-type/visibility, until `exp`. */
1588
1903
  export declare interface UploadTicketPayload {
1589
1904
  /** Frozen-policy ticket contract. Previous tickets must be minted again. */
1590
- readonly version: 1;
1905
+ readonly version: 2;
1591
1906
  /** Optional durable write identity, preserved across ticket renewal. */
1592
1907
  readonly operationId?: string;
1593
- /** Inspect uploads may be pinned to their issuing process. */
1594
- readonly replicaId?: string;
1908
+ /** Exact reference catalogue and physical byte destination authorized by the issuer. */
1909
+ readonly writeScope: string;
1910
+ /** Optional signed routing hint for the binary relay; destination authorization still uses writeScope. */
1911
+ readonly issuerReplicaId?: string;
1595
1912
  /** Exact storage plugin instance authorized to accept the bytes. */
1596
1913
  readonly instance: string;
1914
+ /** Logical destination selected before issuing the upload/session. */
1915
+ readonly destination: string;
1597
1916
  /** ownerId (subject id) the stored object is attributed to. */
1598
1917
  readonly sub: string | null;
1599
1918
  /** tenantId that scopes the key prefix + ref row. */
@@ -1624,28 +1943,28 @@ export declare interface UsageDelta {
1624
1943
 
1625
1944
  /** Verify a finalize token. Returns the payload on success, null on any failure
1626
1945
  * (malformed, bad signature, expired, missing temp key). Never throws. */
1627
- export declare const verifyFinalizeTicket: (token: string, secret?: string) => FinalizeTicketPayload | null;
1946
+ export declare const verifyFinalizeTicket: (token: string, secret?: StorageSecrets) => FinalizeTicketPayload | null;
1628
1947
 
1629
1948
  /**
1630
1949
  * Verify a grant token. Returns the payload on success, null on any
1631
1950
  * failure (malformed, bad signature, expired). Never throws.
1632
1951
  */
1633
- export declare const verifyGrant: (token: string, secret?: string) => GrantTokenPayload | null;
1952
+ export declare const verifyGrant: (token: string, secret?: StorageSecrets) => GrantTokenPayload | null;
1634
1953
 
1635
1954
  /** Verify a multipart ticket. Returns the payload on success, null on any
1636
1955
  * failure (malformed, bad signature, expired, missing key/uploadId). */
1637
- export declare const verifyMultipartTicket: (token: string, secret?: string) => MultipartTicketPayload | null;
1956
+ export declare const verifyMultipartTicket: (token: string, secret?: StorageSecrets) => MultipartTicketPayload | null;
1638
1957
 
1639
1958
  /** Constant-time check of a plaintext password against a stored hash. */
1640
1959
  export declare const verifyPassword: (password: string | undefined, stored: string | null) => boolean;
1641
1960
 
1642
1961
  /** Verify a resumable ticket. Returns the payload on success, null on any
1643
1962
  * failure (malformed, bad signature, expired, missing id/size). Never throws. */
1644
- export declare const verifyResumableTicket: (token: string, secret?: string) => ResumableTicketPayload | null;
1963
+ export declare const verifyResumableTicket: (token: string, secret?: StorageSecrets) => ResumableTicketPayload | null;
1645
1964
 
1646
1965
  /** Verify an upload ticket. Returns the payload on success, null on any
1647
1966
  * failure (malformed, bad signature, expired). Never throws. */
1648
- export declare const verifyUploadTicket: (token: string, secret?: string) => UploadTicketPayload | null;
1967
+ export declare const verifyUploadTicket: (token: string, secret?: StorageSecrets) => UploadTicketPayload | null;
1649
1968
 
1650
1969
  /**
1651
1970
  * App-supplied media probe for video/audio uploads. The core deliberately does
@@ -1690,4 +2009,7 @@ export declare const _voltroStorageUsageTable: TableLike;
1690
2009
  */
1691
2010
  export declare const withStorage: () => ReturnType<typeof mixin>;
1692
2011
 
2012
+ /** All keyed operations share the same namespace, including presigned and multipart paths. */
2013
+ export declare const withStorageKeyPrefix: (provider: StorageProvider, prefix?: string) => StorageProvider;
2014
+
1693
2015
  export { }