@voltro/plugin-storage 0.84.0 → 0.86.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.
package/dist/index.d.ts CHANGED
@@ -9,11 +9,14 @@ import { mixin } from '@voltro/database';
9
9
  import { PluginEgressDeclaration } from '@voltro/protocol';
10
10
  import { Readable } from 'node:stream';
11
11
  import { Schema } from 'effect';
12
+ import { StorageAccessPolicy } from '@voltro/protocol';
13
+ import { StorageAccessRule } from '@voltro/protocol';
12
14
  import { Stream } from 'effect';
13
15
  import { Subject } from '@voltro/protocol';
14
16
  import { Table } from '@voltro/database';
15
17
  import { TableLike } from '@voltro/database';
16
18
  import { VoltroPlugin } from '@voltro/protocol';
19
+ import { YieldableError } from 'effect/Cause';
17
20
 
18
21
  export declare interface AccessOptions {
19
22
  /** Resolve a subject's group ids. Default: `subject.metadata.groups`. */
@@ -22,7 +25,7 @@ export declare interface AccessOptions {
22
25
  readonly guards?: Record<string, StorageGuard>;
23
26
  }
24
27
 
25
- export declare type AccessPolicy = ReadonlyArray<AccessRule>;
28
+ export declare type AccessPolicy = StorageAccessPolicy;
26
29
 
27
30
  /**
28
31
  * One access rule. A rule GRANTS when EVERY present condition is satisfied
@@ -30,24 +33,7 @@ export declare type AccessPolicy = ReadonlyArray<AccessRule>;
30
33
  * grants (OR). An empty policy on a private object means owner-only (the
31
34
  * implicit owner check always applies).
32
35
  */
33
- export declare interface AccessRule {
34
- /** `subject.id === ref.ownerId`. */
35
- readonly owner?: boolean;
36
- /** `subject.metadata.roles` intersects these. */
37
- readonly roles?: ReadonlyArray<string>;
38
- /** resolved groups (default `subject.metadata.groups`) intersect these. */
39
- readonly groups?: ReadonlyArray<string>;
40
- /** `subject.scopes` intersects these. */
41
- readonly scopes?: ReadonlyArray<string>;
42
- /** `subject.tenantId === ref.tenantId`. */
43
- readonly tenant?: boolean;
44
- /** subject is any authenticated api-key. */
45
- readonly apiKey?: boolean;
46
- /** a correct password was supplied (checked at mint, against passwordHash). */
47
- readonly password?: boolean;
48
- /** a named custom guard (`options.guards[name]`) returns true. */
49
- readonly guard?: string;
50
- }
36
+ export declare type AccessRule = StorageAccessRule;
51
37
 
52
38
  /**
53
39
  * The provider the APP configured, falling back to the env-derived default when no
@@ -72,6 +58,20 @@ export declare interface AssetRefOptions {
72
58
  readonly nullable?: boolean;
73
59
  }
74
60
 
61
+ /** The caller owns the after-schema transaction, with serving replicas stopped.
62
+ * Assignments preserve reference IDs, keys, grants, raw put identities, claims
63
+ * and quota. No provider I/O or nested transaction occurs. Returned cleanup
64
+ * targets run through their matching service after boot. */
65
+ export declare const assignStorageObjectNamespacesInTransaction: (store: DataStore, scopes: readonly StorageReferenceScope[], assignments: readonly StorageObjectNamespaceAssignment[]) => Promise<StorageObjectNamespaceMigrationResult>;
66
+
67
+ /** Run in an after-schema migration with serving replicas stopped. The mapping
68
+ * comes from the operator, never from provider failures or the default name.
69
+ * The supplied store belongs to the caller’s transaction. No nested transaction
70
+ * is opened. Only ownership columns change: IDs, bytes, claims, quota and replay stay put. */
71
+ export declare const assignStorageReferenceInstancesInTransaction: (store: DataStore, instances: readonly string[], assignments: readonly StorageReferenceInstanceAssignment[]) => Promise<{
72
+ readonly assigned: number;
73
+ }>;
74
+
75
75
  export declare const azureProvider: (options: AzureProviderOptions) => StorageProvider;
76
76
 
77
77
  export declare interface AzureProviderOptions {
@@ -173,10 +173,10 @@ export declare const databaseProvider: () => StorageProvider & {
173
173
  bindDataStore: (store: unknown) => void;
174
174
  };
175
175
 
176
- export declare const dataStoreGrantStore: (store: DataStore) => GrantStore;
176
+ export declare const dataStoreGrantStore: (store: DataStore, configuredScope: StorageReferenceScope) => GrantStore;
177
177
 
178
178
  /** Ref store backed by the app's `DataStore` (`_voltro_storage_refs`). */
179
- export declare const dataStoreRefStore: (store: DataStore) => RefStore;
179
+ export declare const dataStoreRefStore: (store: DataStore, configuredScope: StorageReferenceScope) => RefStore;
180
180
 
181
181
  /** Decode a `data:<mime>;base64,<data>` URI to bytes + content type. */
182
182
  export declare const decodeDataUri: (dataUri: string) => {
@@ -193,10 +193,22 @@ export declare const decodeDataUri: (dataUri: string) => {
193
193
  */
194
194
  export declare const durabilityNotice: (path: string) => string | undefined;
195
195
 
196
+ /**
197
+ * Apply a creative edit; null when the input is not an image, `sharp` is not
198
+ * installed, the edit is empty or fails. The format stays the input's.
199
+ */
200
+ export declare const editImage: (bytes: Uint8Array, contentType: string, edit: ImageEdit) => Promise<Transformed | null>;
201
+
196
202
  /** Frame bytes for clamd `INSTREAM`: `zINSTREAM\0` then `<u32 len><data>`
197
203
  * chunks, terminated by a zero-length chunk. Exposed for testing. */
198
204
  export declare const encodeInstream: (bytes: Uint8Array, chunkSize?: number) => Buffer;
199
205
 
206
+ /** Boot-time initialization after normal schema reconciliation. This identity
207
+ * describes the persisted reference catalogue, not its provider bytes or host. */
208
+ export declare const ensureStorageReferenceCatalogue: (store: DataStore) => Promise<void>;
209
+
210
+ export declare const fetchIngestBody: (url: string, configuredMaxBytes?: number, policy?: IngestPolicy) => Effect.Effect<StoredObject, StorageError | StorageRejected>;
211
+
200
212
  export declare interface FfmpegRenditionSpec {
201
213
  /** Derivative class stored on the rendition ref — `'rendition'` / `'poster'`. */
202
214
  readonly kind: string;
@@ -286,6 +298,19 @@ export declare interface GrantTokenPayload {
286
298
  /** Hash a file password as `salt:scryptHash` (hex). */
287
299
  export declare const hashPassword: (password: string) => string;
288
300
 
301
+ /** A creative edit: the result is new bytes, stored as a new version — unlike a delivery transform. */
302
+ export declare interface ImageEdit {
303
+ /** Clockwise, in degrees. */
304
+ readonly rotate?: 90 | 180 | 270;
305
+ /** Mirror top to bottom. */
306
+ readonly flip?: boolean;
307
+ /** Mirror left to right. */
308
+ readonly flop?: boolean;
309
+ readonly grayscale?: boolean;
310
+ /** Cut this region out, in pixels of the original. */
311
+ readonly crop?: Region;
312
+ }
313
+
289
314
  /** Where `ingestUrl` may fetch from — see `ssrfReason` in `@voltro/runtime`. */
290
315
  export declare interface IngestPolicy {
291
316
  /** Hosts (or `host:port`, or `*.suffix`) reachable although internal — a
@@ -333,15 +358,21 @@ export declare interface ListRefsResult {
333
358
 
334
359
  /** DataStore-backed metadata boundary, intentionally no provider access. The
335
360
  * service must use reserve -> provider.put -> confirm and retain failed intents. */
336
- export declare const makeStorageObjectMetadata: (store: DataStore) => {
361
+ export declare const makeStorageObjectMetadata: (store: DataStore, configuredScope: StorageReferenceScope) => {
362
+ scope: Readonly<{
363
+ storageInstance: string;
364
+ objectNamespace: string;
365
+ }>;
337
366
  /** The put intent a reference was written under, if it was written through an operation at all. */
338
367
  intent: (refId: string) => Promise<{
339
368
  identity: {
369
+ readonly storageInstance: string;
370
+ readonly objectNamespace: string;
340
371
  readonly tenantId: string | null;
341
372
  readonly bucket: string;
373
+ readonly operationId: string;
342
374
  readonly fingerprint: string;
343
375
  readonly checksum: string;
344
- readonly operationId: string;
345
376
  readonly logicalKey: string;
346
377
  };
347
378
  ref: StorageRef;
@@ -361,32 +392,38 @@ export declare const makeStorageObjectMetadata: (store: DataStore) => {
361
392
  beginDelete: (refId: string, tenantId?: string | null, claim?: string) => Promise<{
362
393
  released: true;
363
394
  identity: {
395
+ readonly storageInstance: string;
396
+ readonly objectNamespace: string;
364
397
  readonly tenantId: string | null;
365
398
  readonly bucket: string;
399
+ readonly operationId: string;
366
400
  readonly fingerprint: string;
367
401
  readonly checksum: string;
368
- readonly operationId: string;
369
402
  readonly logicalKey: string;
370
403
  };
371
404
  operation?: never;
372
405
  } | {
373
406
  released: true;
374
407
  operation: {
375
- readonly key: string;
376
408
  readonly id: string;
409
+ readonly storageInstance: string;
410
+ readonly objectNamespace: string;
411
+ readonly objectId: string;
377
412
  readonly fingerprint: string;
378
413
  readonly generation: string;
379
- readonly state: "ready" | "reserved" | "deleted" | "deleting";
414
+ readonly key: string;
380
415
  readonly refId: string;
381
- readonly objectId: string;
416
+ readonly state: "ready" | "reserved" | "deleting" | "deleted";
382
417
  readonly deleteKey: string | null;
383
418
  };
384
419
  identity: {
420
+ readonly storageInstance: string;
421
+ readonly objectNamespace: string;
385
422
  readonly tenantId: string | null;
386
423
  readonly bucket: string;
424
+ readonly operationId: string;
387
425
  readonly fingerprint: string;
388
426
  readonly checksum: string;
389
- readonly operationId: string;
390
427
  readonly logicalKey: string;
391
428
  };
392
429
  }>;
@@ -541,6 +578,9 @@ export declare interface RefStatistics {
541
578
  }
542
579
 
543
580
  export declare interface RefStore {
581
+ /** Stable identity of this actual reference view. Shared catalogues expose
582
+ * the same identity; independent/process-local stores must expose their own. */
583
+ readonly catalogueId: Effect.Effect<string, StorageError>;
544
584
  /** Aggregate the complete reference index without a recent-object limit. */
545
585
  readonly statistics: () => Effect.Effect<RefStatistics, StorageError>;
546
586
  readonly insert: (ref: StorageRef) => Effect.Effect<void, StorageError>;
@@ -593,6 +633,14 @@ export declare interface RefStore {
593
633
  readonly countPublicByKey?: ((bucket: string, key: string) => Effect.Effect<number, StorageError>) | undefined;
594
634
  }
595
635
 
636
+ /** A region of an image in pixels. */
637
+ declare interface Region {
638
+ readonly left: number;
639
+ readonly top: number;
640
+ readonly width: number;
641
+ readonly height: number;
642
+ }
643
+
596
644
  /** One derived output a `Transcoder` produces from a source asset. Each becomes
597
645
  * a normal storage ref linked back to the original via `derivedFrom`. */
598
646
  export declare interface Rendition {
@@ -604,6 +652,9 @@ export declare interface Rendition {
604
652
  readonly label?: string;
605
653
  }
606
654
 
655
+ /** Nullable additions deliberately fail closed until the explicit upgrade. */
656
+ export declare const requireStorageObjectNamespaces: (store: DataStore) => Promise<void>;
657
+
607
658
  /** A `ListRefsInput` after the service normalizes its defaults / clamps — what
608
659
  * a `RefStore.listRefs` impl receives. `limit` is already the `+1` probe. */
609
660
  export declare interface ResolvedListQuery {
@@ -655,23 +706,25 @@ export declare interface S3ProviderOptions {
655
706
  readonly publicAcl?: boolean;
656
707
  }
657
708
 
709
+ declare type ScopedStorageObjectIdentity = StorageObjectIdentity & StorageReferenceScope;
710
+
658
711
  /** Register the app's storage provider (or clear with `undefined`). */
659
712
  export declare const setActiveStorageProvider: (provider: StorageProvider | undefined) => void;
660
713
 
661
714
  /** Sign a finalize token valid for `ttlSeconds`. */
662
- export declare const signFinalizeTicket: (fields: Omit<FinalizeTicketPayload, "exp">, ttlSeconds: number, secret?: string) => string;
715
+ export declare const signFinalizeTicket: (fields: Omit<FinalizeTicketPayload, "exp" | "version">, ttlSeconds: number, secret?: string) => string;
663
716
 
664
717
  /** Mint a token granting read access to `refId` for `ttlSeconds`. */
665
718
  export declare const signGrant: (refId: string, ttlSeconds: number, secret?: string, audience?: string) => string;
666
719
 
667
720
  /** Sign a multipart ticket valid for `ttlSeconds`. */
668
- export declare const signMultipartTicket: (fields: Omit<MultipartTicketPayload, "exp">, ttlSeconds: number, secret?: string) => string;
721
+ export declare const signMultipartTicket: (fields: Omit<MultipartTicketPayload, "exp" | "version">, ttlSeconds: number, secret?: string) => string;
669
722
 
670
723
  /** Sign a resumable ticket valid for `ttlSeconds`. */
671
- export declare const signResumableTicket: (fields: Omit<ResumableTicketPayload, "exp">, ttlSeconds: number, secret?: string) => string;
724
+ export declare const signResumableTicket: (fields: Omit<ResumableTicketPayload, "exp" | "version">, ttlSeconds: number, secret?: string) => string;
672
725
 
673
726
  /** Sign an upload ticket valid for `ttlSeconds`. */
674
- export declare const signUploadTicket: (fields: Omit<UploadTicketPayload, "exp">, ttlSeconds: number, secret?: string) => string;
727
+ export declare const signUploadTicket: (fields: Omit<UploadTicketPayload, "exp" | "version">, ttlSeconds: number, secret?: string) => string;
675
728
 
676
729
  /** True if the bytes match the declared content type's magic number, OR we
677
730
  * have no signature for that type (can't verify → don't block). */
@@ -681,6 +734,8 @@ export declare const STORAGE_BLOBS_TABLE = "_voltro_storage_blobs";
681
734
 
682
735
  export declare const STORAGE_GRANTS_TABLE = "_voltro_storage_grants";
683
736
 
737
+ export declare const STORAGE_REFERENCE_CATALOGUE_TABLE = "_voltro_storage_reference_catalogue";
738
+
684
739
  export declare const STORAGE_REFS_TABLE = "_voltro_storage_refs";
685
740
 
686
741
  export declare const STORAGE_USAGE_TABLE = "_voltro_storage_usage";
@@ -693,12 +748,10 @@ export declare const STORAGE_USAGE_TABLE = "_voltro_storage_usage";
693
748
  export declare class StorageAccessDenied extends StorageAccessDenied_base {
694
749
  }
695
750
 
696
- declare const StorageAccessDenied_base: Schema.TaggedErrorClass<StorageAccessDenied, "StorageAccessDenied", {
697
- readonly _tag: Schema.tag<"StorageAccessDenied">;
698
- } & {
699
- refId: typeof Schema.String;
700
- reason: typeof Schema.String;
701
- }>;
751
+ declare const StorageAccessDenied_base: Schema.Class<StorageAccessDenied, Schema.TaggedStruct<"StorageAccessDenied", {
752
+ readonly refId: Schema.String;
753
+ readonly reason: Schema.String;
754
+ }>, YieldableError>;
702
755
 
703
756
  /**
704
757
  * Where the bytes go, read from the same options and env `resolveProvider`
@@ -715,14 +768,12 @@ export declare const storageEgressOf: (options: StoragePluginOptions) => Readonl
715
768
  export declare class StorageError extends StorageError_base {
716
769
  }
717
770
 
718
- declare const StorageError_base: Schema.TaggedErrorClass<StorageError, "StorageError", {
719
- readonly _tag: Schema.tag<"StorageError">;
720
- } & {
721
- provider: typeof Schema.String;
722
- message: typeof Schema.String;
723
- transient: typeof Schema.Boolean;
724
- status: Schema.optional<typeof Schema.Number>;
725
- }>;
771
+ declare const StorageError_base: Schema.Class<StorageError, Schema.TaggedStruct<"StorageError", {
772
+ readonly provider: Schema.String;
773
+ readonly message: Schema.String;
774
+ readonly transient: Schema.Boolean;
775
+ readonly status: Schema.optional<Schema.Number>;
776
+ }>, YieldableError>;
726
777
 
727
778
  /** A persisted, explicit share of one object with one principal. */
728
779
  export declare interface StorageGrant {
@@ -752,8 +803,9 @@ export declare type StorageGuard = (ctx: {
752
803
  * and `BASE_NAME` is not exported, so a caller minting a ticket outside the
753
804
  * plugin had to guess it. Guessing is exactly what a `string` parameter invites.
754
805
  *
806
+ * const writeScope = await Effect.runPromise(service.uploadTicketScope())
755
807
  * const ticket = signUploadTicket(
756
- * { instance: storageInstanceName(), key, contentType },
808
+ * { writeScope, instance: storageInstanceName(options), key, ct: contentType, sub: ownerId, tenant: tenantId, vis: 'private' },
757
809
  * 300,
758
810
  * )
759
811
  *
@@ -782,11 +834,14 @@ export declare const storageObjectLifecycleTables: ( Table<"_voltro_storage_oper
782
834
  readonly nonce: ColumnBuilder<string, "text", boolean>;
783
835
  }>, false, never> | Table<"_voltro_storage_object_heads", FieldDefinitions<{
784
836
  readonly id: ColumnBuilder<string, "text", boolean>;
837
+ readonly objectNamespace: ColumnBuilder<string | null, "text", boolean>;
785
838
  readonly generation: ColumnBuilder<string, "text", boolean>;
786
839
  readonly key: ColumnBuilder<string, "text", boolean>;
787
840
  readonly reservations: ColumnBuilder<string, "bigint", boolean>;
788
841
  }>, false, never> | Table<"_voltro_storage_operations", FieldDefinitions<{
789
842
  readonly id: ColumnBuilder<string, "text", boolean>;
843
+ readonly storageInstance: ColumnBuilder<string | null, "text", boolean>;
844
+ readonly objectNamespace: ColumnBuilder<string | null, "text", boolean>;
790
845
  readonly objectId: ColumnBuilder<string, "text", boolean>;
791
846
  readonly fingerprint: ColumnBuilder<string, "text", boolean>;
792
847
  readonly generation: ColumnBuilder<string, "text", boolean>;
@@ -802,6 +857,19 @@ export declare const storageObjectLifecycleTables: ( Table<"_voltro_storage_oper
802
857
 
803
858
  export declare type StorageObjectMetadata = ReturnType<typeof makeStorageObjectMetadata>;
804
859
 
860
+ /** Credential-independent identity of a physical object-key namespace.
861
+ * Equal identities mean an equal key addresses the same provider bytes. */
862
+ export declare const storageObjectNamespace: (parts: readonly string[]) => string;
863
+
864
+ export declare interface StorageObjectNamespaceAssignment extends StorageReferenceScope {
865
+ readonly refId: string;
866
+ }
867
+
868
+ export declare interface StorageObjectNamespaceMigrationResult {
869
+ readonly assigned: number;
870
+ readonly pendingDeletes: readonly StorageObjectNamespaceAssignment[];
871
+ }
872
+
805
873
  export declare const storagePlugin: (options?: StoragePluginOptions) => VoltroPlugin;
806
874
 
807
875
  export declare interface StoragePluginOptions {
@@ -866,12 +934,25 @@ export declare interface StoragePluginOptions {
866
934
  /** Access engine config — a group resolver + named custom guards. */
867
935
  readonly access?: AccessOptions;
868
936
  /**
869
- * The access policy every PRIVATE client upload (an upload ticket) is
870
- * stored with — declared by the app, never chosen by the client. Without
937
+ * The default policy for PRIVATE client uploads, or an Effect function
938
+ * authorizing self-service upload visibility and requested policies before the server signs a ticket. Without
871
939
  * it only the uploader (the implicit owner rule) can read a private upload;
872
940
  * `[{ tenant: true }]` lets everyone in the uploader's tenant read it.
873
941
  */
874
- readonly uploadAccess?: AccessPolicy;
942
+ readonly uploadAccess?: AccessPolicy | ((request: {
943
+ readonly subject: Subject;
944
+ readonly requested: AccessPolicy | undefined;
945
+ readonly visibility: 'public' | 'private';
946
+ readonly contentType: string;
947
+ }) => Effect.Effect<AccessPolicy, StorageError>);
948
+ /**
949
+ * How long a browser or shared cache may keep a PUBLIC object the serve
950
+ * route delivers itself (and each transform of it). Default: a year,
951
+ * `immutable`. An object can turn private later; a copy cached before then
952
+ * keeps opening until this runs out — set it to bound that window (the
953
+ * bucket's own caching, behind a direct URL, is the provider's).
954
+ */
955
+ readonly publicCacheSeconds?: number;
875
956
  /** Upload constraints — size cap, content-type allow-list, magic-byte sniff. */
876
957
  readonly limits?: UploadLimits;
877
958
  /**
@@ -943,6 +1024,8 @@ export declare interface StoragePluginOptions {
943
1024
  * access policy.
944
1025
  */
945
1026
  export declare interface StorageProvider {
1027
+ /** Stable physical object-key namespace; exclude credentials and plugin aliases. */
1028
+ readonly objectNamespace: Effect.Effect<string, StorageError>;
946
1029
  /**
947
1030
  * Where a FILESYSTEM provider writes. Absent for every other provider.
948
1031
  *
@@ -1033,7 +1116,9 @@ export declare type StorageProviderName = 's3' | 'minio' | 'azure' | 'filesystem
1033
1116
  export declare const storagePutIntentsTable: Table<"_voltro_storage_put_intents", FieldDefinitions<{
1034
1117
  readonly id: ColumnBuilder<string, "text", boolean>;
1035
1118
  readonly refId: ColumnBuilder<string, "text", boolean>;
1036
- readonly identity: ColumnBuilder<StorageObjectIdentity, "json", boolean>;
1119
+ readonly storageInstance: ColumnBuilder<string | null, "text", boolean>;
1120
+ readonly objectNamespace: ColumnBuilder<string | null, "text", boolean>;
1121
+ readonly identity: ColumnBuilder<ScopedStorageObjectIdentity, "json", boolean>;
1037
1122
  readonly ref: ColumnBuilder<StorageRef, "json", boolean>;
1038
1123
  }>, false, never>;
1039
1124
 
@@ -1083,18 +1168,32 @@ export declare interface StorageRef {
1083
1168
  readonly kind?: string | null;
1084
1169
  }
1085
1170
 
1171
+ export declare const storageReferenceCatalogueTable: Table<"_voltro_storage_reference_catalogue", FieldDefinitions<{
1172
+ readonly id: ColumnBuilder<string, "text", boolean>;
1173
+ readonly namespace: ColumnBuilder<string, "text", boolean>;
1174
+ }>, false, never>;
1175
+
1176
+ export declare interface StorageReferenceInstanceAssignment {
1177
+ readonly refId: string;
1178
+ readonly storageInstance: string;
1179
+ }
1180
+
1181
+ export declare interface StorageReferenceScope {
1182
+ readonly storageInstance: string;
1183
+ readonly objectNamespace: string;
1184
+ }
1185
+
1086
1186
  /** An upload was rejected by a constraint (size / content-type / magic-byte
1087
1187
  * mismatch). `reason` is a stable code; `detail` is human-readable. */
1088
1188
  export declare class StorageRejected extends StorageRejected_base {
1189
+ get message(): string;
1089
1190
  }
1090
1191
 
1091
- declare const StorageRejected_base: Schema.TaggedErrorClass<StorageRejected, "StorageRejected", {
1092
- readonly _tag: Schema.tag<"StorageRejected">;
1093
- } & {
1192
+ declare const StorageRejected_base: Schema.Class<StorageRejected, Schema.TaggedStruct<"StorageRejected", {
1094
1193
  /** `'too-large' | 'content-type-not-allowed' | 'content-mismatch'` */
1095
- reason: typeof Schema.String;
1096
- detail: typeof Schema.String;
1097
- }>;
1194
+ readonly reason: Schema.String;
1195
+ readonly detail: Schema.String;
1196
+ }>, YieldableError>;
1098
1197
 
1099
1198
  /** Scans bytes before they're stored. Return `{ clean:false, threat }` to
1100
1199
  * reject the upload (fails with `StorageScanRejected`, nothing is stored). */
@@ -1108,11 +1207,9 @@ export declare type StorageScanner = (input: {
1108
1207
  export declare class StorageScanRejected extends StorageScanRejected_base {
1109
1208
  }
1110
1209
 
1111
- declare const StorageScanRejected_base: Schema.TaggedErrorClass<StorageScanRejected, "StorageScanRejected", {
1112
- readonly _tag: Schema.tag<"StorageScanRejected">;
1113
- } & {
1114
- threat: typeof Schema.String;
1115
- }>;
1210
+ declare const StorageScanRejected_base: Schema.Class<StorageScanRejected, Schema.TaggedStruct<"StorageScanRejected", {
1211
+ readonly threat: Schema.String;
1212
+ }>, YieldableError>;
1116
1213
 
1117
1214
  export declare interface StorageScanResult {
1118
1215
  readonly clean: boolean;
@@ -1123,7 +1220,7 @@ export declare interface StorageScanResult {
1123
1220
  export declare class StorageService extends StorageService_base {
1124
1221
  }
1125
1222
 
1126
- declare const StorageService_base: Context.TagClass<StorageService, "@voltro/plugin-storage/StorageService", StorageServiceShape>;
1223
+ declare const StorageService_base: Context.ServiceClass<StorageService, "@voltro/plugin-storage/StorageService", StorageServiceShape>;
1127
1224
 
1128
1225
  export declare const storageServiceLayer: (options: StorageServiceOptions) => Layer.Layer<StorageService>;
1129
1226
 
@@ -1132,6 +1229,8 @@ export declare interface StorageServiceOptions {
1132
1229
  readonly refStore: RefStore;
1133
1230
  readonly grantStore: GrantStore;
1134
1231
  readonly bucket: string;
1232
+ /** Canonical plugin instance; separates new plain keys on a shared provider. */
1233
+ readonly storageInstance?: string;
1135
1234
  /** Static prefix prepended to every key (e.g. an app/env namespace). */
1136
1235
  readonly tenantPrefix?: string;
1137
1236
  /** Transient-failure retries (429 / 5xx / network). Default 3. */
@@ -1184,6 +1283,8 @@ export declare interface StorageServiceOptions {
1184
1283
 
1185
1284
  /** The service handlers consume: `const storage = yield* StorageService`. */
1186
1285
  export declare interface StorageServiceShape {
1286
+ /** Opaque destination binding for manually signed upload-family credentials. */
1287
+ readonly uploadTicketScope: () => Effect.Effect<string, StorageError>;
1187
1288
  /** Store bytes. The result is the ref plus this call's `outcome`. */
1188
1289
  readonly put: (input: PutInput) => Effect.Effect<StoredRef, StorageError | StorageRejected | StorageScanRejected>;
1189
1290
  /** Fetch an object's bytes by id. Enforces the tenant guard ONLY — it does
@@ -1370,6 +1471,7 @@ export declare interface StorageServiceShape {
1370
1471
  readonly tenantId?: string | null;
1371
1472
  readonly ownerId?: string | null;
1372
1473
  readonly visibility?: StorageVisibility;
1474
+ readonly access?: AccessPolicy;
1373
1475
  readonly folder?: string;
1374
1476
  readonly tags?: ReadonlyArray<string>;
1375
1477
  readonly alt?: string;
@@ -1403,6 +1505,7 @@ export declare interface StorageServiceShape {
1403
1505
  readonly tenantId?: string | null;
1404
1506
  readonly ownerId?: string | null;
1405
1507
  readonly visibility?: StorageVisibility;
1508
+ readonly access?: AccessPolicy;
1406
1509
  readonly folder?: string;
1407
1510
  readonly tags?: ReadonlyArray<string>;
1408
1511
  readonly alt?: string;
@@ -1452,6 +1555,7 @@ export declare interface StorageServiceShape {
1452
1555
  readonly tenantId?: string | null;
1453
1556
  readonly ownerId?: string | null;
1454
1557
  readonly visibility?: StorageVisibility;
1558
+ readonly access?: AccessPolicy;
1455
1559
  readonly folder?: string;
1456
1560
  readonly tags?: ReadonlyArray<string>;
1457
1561
  readonly alt?: string;
@@ -1564,10 +1668,14 @@ export declare interface UploadLimits {
1564
1668
  /** What an upload ticket authorizes: a single through-app upload bound to an
1565
1669
  * owner/tenant/content-type/visibility, until `exp`. */
1566
1670
  export declare interface UploadTicketPayload {
1671
+ /** Frozen-policy ticket contract. Previous tickets must be minted again. */
1672
+ readonly version: 2;
1567
1673
  /** Optional durable write identity, preserved across ticket renewal. */
1568
1674
  readonly operationId?: string;
1569
- /** Inspect uploads may be pinned to their issuing process. */
1570
- readonly replicaId?: string;
1675
+ /** Exact reference catalogue and physical byte destination authorized by the issuer. */
1676
+ readonly writeScope: string;
1677
+ /** Optional signed routing hint for the binary relay; destination authorization still uses writeScope. */
1678
+ readonly issuerReplicaId?: string;
1571
1679
  /** Exact storage plugin instance authorized to accept the bytes. */
1572
1680
  readonly instance: string;
1573
1681
  /** ownerId (subject id) the stored object is attributed to. */
@@ -1577,6 +1685,8 @@ export declare interface UploadTicketPayload {
1577
1685
  /** Authorized content-type — the stored object uses this. */
1578
1686
  readonly ct: string;
1579
1687
  readonly vis: 'public' | 'private';
1688
+ /** Server-authorized policy, frozen into the signed ticket. */
1689
+ readonly access?: AccessPolicy;
1580
1690
  /** Optional fixed logical key (else content-addressed). */
1581
1691
  readonly key?: string;
1582
1692
  /** Max bytes the route will accept for this ticket (0/undefined = no cap). */