@filelayer/core 0.3.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 (69) hide show
  1. package/CHANGELOG.md +338 -0
  2. package/LICENSE +202 -0
  3. package/MIGRATIONS.md +328 -0
  4. package/NOTICE +37 -0
  5. package/README.md +343 -0
  6. package/SEMANTICS.md +729 -0
  7. package/dist/authz.d.ts +524 -0
  8. package/dist/authz.d.ts.map +1 -0
  9. package/dist/authz.js +889 -0
  10. package/dist/authz.js.map +1 -0
  11. package/dist/db.d.ts +145 -0
  12. package/dist/db.d.ts.map +1 -0
  13. package/dist/db.js +217 -0
  14. package/dist/db.js.map +1 -0
  15. package/dist/delivery.d.ts +293 -0
  16. package/dist/delivery.d.ts.map +1 -0
  17. package/dist/delivery.js +519 -0
  18. package/dist/delivery.js.map +1 -0
  19. package/dist/errors.d.ts +16 -0
  20. package/dist/errors.d.ts.map +1 -0
  21. package/dist/errors.js +21 -0
  22. package/dist/errors.js.map +1 -0
  23. package/dist/filelayer.d.ts +542 -0
  24. package/dist/filelayer.d.ts.map +1 -0
  25. package/dist/filelayer.js +1360 -0
  26. package/dist/filelayer.js.map +1 -0
  27. package/dist/index.d.ts +8 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +8 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/simple.d.ts +297 -0
  32. package/dist/simple.d.ts.map +1 -0
  33. package/dist/simple.js +492 -0
  34. package/dist/simple.js.map +1 -0
  35. package/dist/storage.d.ts +269 -0
  36. package/dist/storage.d.ts.map +1 -0
  37. package/dist/storage.js +700 -0
  38. package/dist/storage.js.map +1 -0
  39. package/dist/store.d.ts +432 -0
  40. package/dist/store.d.ts.map +1 -0
  41. package/dist/store.js +862 -0
  42. package/dist/store.js.map +1 -0
  43. package/package.json +77 -0
  44. package/schema.sql +1190 -0
  45. package/src/authz.ts +1398 -0
  46. package/src/db.ts +271 -0
  47. package/src/delivery.ts +737 -0
  48. package/src/errors.ts +24 -0
  49. package/src/filelayer.ts +1836 -0
  50. package/src/index.ts +7 -0
  51. package/src/simple.ts +666 -0
  52. package/src/storage.ts +917 -0
  53. package/src/store.ts +1072 -0
  54. package/test/delivery.test.ts +0 -0
  55. package/test/group-subjects.test.ts +1072 -0
  56. package/test/helpers.ts +65 -0
  57. package/test/listing.test.ts +689 -0
  58. package/test/local-s3.d.mts +33 -0
  59. package/test/local-s3.mjs +400 -0
  60. package/test/persistence.test.ts +953 -0
  61. package/test/regression.test.ts +619 -0
  62. package/test/s3-live.test.ts +322 -0
  63. package/test/security.test.ts +1652 -0
  64. package/test/semantics.test.ts +888 -0
  65. package/test/storage.test.ts +437 -0
  66. package/test/tiers.test.ts +432 -0
  67. package/test/vault-example.test.ts +302 -0
  68. package/tsconfig.build.json +29 -0
  69. package/tsconfig.json +19 -0
@@ -0,0 +1,542 @@
1
+ /**
2
+ * FILELAYER -- the surface an application developer actually touches.
3
+ *
4
+ * Design rule for this file: every method that touches a file or an org calls
5
+ * into the authorization engine and does nothing before it. There is no
6
+ * "internal" variant that skips the check, because the moment such a variant
7
+ * exists someone will call it from a route handler at 6pm on a Friday.
8
+ *
9
+ * SECOND design rule, added after the security review: this file contains no
10
+ * security LOGIC, only security PLUMBING. Every rule that used to live here as
11
+ * a "patch at the wrong layer" has moved into `authz.ts` or `schema.sql`:
12
+ *
13
+ * - capability attenuation on share -> authorizeShare() + a BEFORE
14
+ * INSERT trigger on file_grant
15
+ * - the 410/409 existence-oracle downgrade
16
+ * and its `hasStanding()` helper -> evaluation order in authorize()
17
+ * - the membership / viewer checks in
18
+ * upload() -> authorizeOrg('create_file')
19
+ * - the admin check in auditLog() -> authorizeOrg('read_audit')
20
+ * - the unaudited early return in redeem()
21
+ * for unknown secrets -> the engine's system chain
22
+ *
23
+ * Errors are thrown as `FilelayerError`, already collapsed through
24
+ * `toPublicError`, so the developer cannot accidentally return our internal
25
+ * deny reason to an attacker. That collapsing is a security-sensitive decision
26
+ * we make once, here, instead of asking the developer to make it per-route.
27
+ */
28
+ import { type Capability, type FileRef, type FileVisibility, type GrantSubjectType, type OrgRole, type Principal } from './authz.ts';
29
+ import { type Queryable } from './db.ts';
30
+ import { PostgresStore, type AuditRow, type AuditChainResult } from './store.ts';
31
+ import { type PutBody, type StorageAdapter } from './storage.ts';
32
+ import { FilesApi, OrgsApi, SharesApi } from './simple.ts';
33
+ import { type Disposition, type RedirectDeliveryConfig, type StreamedDelivery } from './delivery.ts';
34
+ import { FilelayerError } from './errors.ts';
35
+ export { FilelayerError };
36
+ export interface UploadInput {
37
+ name: string;
38
+ contentType: string;
39
+ /**
40
+ * Advisory. The AUTHORITATIVE size is what the adapter reports it actually
41
+ * wrote, and that is what lands in `size_bytes`. A caller-supplied size that
42
+ * disagrees with the object is how a `content-length` ends up truncating a
43
+ * download.
44
+ */
45
+ size?: number;
46
+ /**
47
+ * Bytes, or a stream of bytes.
48
+ *
49
+ * A `Uint8Array` is the convenient form for the small-file case that
50
+ * dominates (avatars, PDFs, attachments) and is kept for exactly that reason.
51
+ * A `ReadableStream` is the form that does not put the whole object on the
52
+ * heap: with the S3 adapter it becomes a multipart upload whose peak memory
53
+ * is one part, whatever the object's size.
54
+ */
55
+ body: PutBody;
56
+ /**
57
+ * Who, inside the owning org, can see this file before anybody shares it.
58
+ *
59
+ * 'private' (DEFAULT) -- owner + org admins/owners only. Everyone else
60
+ * needs an explicit grant.
61
+ * 'org' -- every member of the org may read it.
62
+ *
63
+ * The default is the restrictive one. See schema.sql, `file_visibility`.
64
+ */
65
+ visibility?: FileVisibility;
66
+ /** Seconds until the file itself expires (lifecycle, not a grant). */
67
+ expiresIn?: number;
68
+ /** Seconds of retention floor: deletion is blocked until it passes. */
69
+ retainFor?: number;
70
+ metadata?: Record<string, unknown>;
71
+ }
72
+ /**
73
+ * WHO a grant is for. A grant's subject is a PRINCIPAL SET (RFC-001):
74
+ *
75
+ * actor exactly one person
76
+ * role every member of an org at role >= `minRole`
77
+ * org every member of an org, at any role
78
+ * link whoever holds the secret (bearer, not identity)
79
+ * anonymous everyone
80
+ *
81
+ * `org` and `role` may name an org OTHER than the file's own -- that is the
82
+ * point ("the company that posted this job may read this CV") -- but it must be
83
+ * an org in the same PROJECT. Cross-project is unrepresentable, by composite
84
+ * foreign key, and refused here with a 404 before it gets that far.
85
+ *
86
+ * I6: an issuer whose own authority came from a GRANT may only mint `actor` or
87
+ * `link`. See `authorizeShare`.
88
+ */
89
+ export type ShareSubject = {
90
+ type: 'link';
91
+ } | {
92
+ type: 'anonymous';
93
+ } | {
94
+ type: 'actor';
95
+ actorId: string;
96
+ }
97
+ /** Every member of `orgId`, at any role. */
98
+ | {
99
+ type: 'org';
100
+ orgId: string;
101
+ }
102
+ /** Every member of `orgId` at `minRole` or above. */
103
+ | {
104
+ type: 'role';
105
+ orgId: string;
106
+ minRole: OrgRole;
107
+ };
108
+ export interface ShareInput {
109
+ subject: ShareSubject;
110
+ capabilities?: Capability[];
111
+ expiresIn?: number;
112
+ maxDownloads?: number;
113
+ password?: string;
114
+ }
115
+ export interface ShareResult {
116
+ grantId: string;
117
+ /** Returned exactly once. Only its SHA-256 is persisted. */
118
+ secret?: string;
119
+ url?: string;
120
+ /**
121
+ * The EFFECTIVE lifetime and cap, after attenuation against the parent grant.
122
+ * A delegated share can never exceed the authority it came from, so these may
123
+ * be tighter than what was asked for. They are returned rather than silently
124
+ * applied so that the clamp is visible to the caller.
125
+ */
126
+ expiresAt: Date | null;
127
+ maxDownloads: number | null;
128
+ /** Non-null when this grant was delegated from another grant (P4). */
129
+ parentGrantId: string | null;
130
+ }
131
+ export interface FileRecord extends FileRef {
132
+ name: string;
133
+ contentType: string;
134
+ sizeBytes: number | null;
135
+ /**
136
+ * WHICH store the bytes are in. This column used to be written as the literal
137
+ * `'memory'` on every insert regardless of the configured adapter, which
138
+ * meant a production deployment recorded every object as living in an
139
+ * in-process Map. It participates in `file_storage_key_idx`
140
+ * (UNIQUE (storage_provider, storage_key)), so it is half of an object's
141
+ * identity, not a label.
142
+ */
143
+ storageProvider: string;
144
+ storageKey: string;
145
+ createdAt: Date;
146
+ }
147
+ export interface GrantSummary {
148
+ id: string;
149
+ fileId: string;
150
+ parentGrantId: string | null;
151
+ subjectType: GrantSubjectType;
152
+ subjectId: string | null;
153
+ /** The org whose members are the subject, for 'org' and 'role' grants. */
154
+ subjectOrgId: string | null;
155
+ /** The role floor, for 'role' grants. Null on 'org' reads as 'viewer'. */
156
+ subjectMinRole: OrgRole | null;
157
+ capabilities: Capability[];
158
+ hasPassword: boolean;
159
+ expiresAt: Date | null;
160
+ maxDownloads: number | null;
161
+ downloadCount: number;
162
+ revokedAt: Date | null;
163
+ /** Recursive liveness: false if this grant OR any ancestor is dead. */
164
+ live: boolean;
165
+ createdBy: string | null;
166
+ createdAt: Date;
167
+ }
168
+ export interface FilelayerOptions {
169
+ baseUrl?: string;
170
+ /**
171
+ * The customer application this instance speaks for (P8).
172
+ *
173
+ * In a hosted deployment the API layer resolves a project from the request's API
174
+ * key and constructs one of these bound to it. A bound instance cannot see,
175
+ * list, audit or address anything in another project. Omit it and everything
176
+ * lands in the default project, which is the correct behaviour for a
177
+ * single-project deployment. Pass `null` for the control plane.
178
+ */
179
+ projectId?: string | null;
180
+ /**
181
+ * OPT-IN REDIRECT DELIVERY. Absent means every delivery is proxied, which is
182
+ * the only mode with an unqualified "revocation is immediate" guarantee.
183
+ *
184
+ * Read the DELIVERY MODES block at the top of `delivery.ts` before setting
185
+ * this. It will not typecheck without the acknowledgement string, and the
186
+ * acknowledgement string says what you are accepting.
187
+ */
188
+ redirectDelivery?: RedirectDeliveryConfig;
189
+ }
190
+ export declare class Filelayer {
191
+ #private;
192
+ readonly store: PostgresStore;
193
+ private readonly db;
194
+ private readonly storage;
195
+ private readonly opts;
196
+ private _files?;
197
+ private _orgs?;
198
+ private _shares?;
199
+ /** Null unless redirect delivery was configured AND acknowledged. */
200
+ private readonly redirect;
201
+ constructor(db: Queryable, storage: StorageAdapter, opts?: FilelayerOptions);
202
+ /**
203
+ * A throwaway, in-process instance: PGlite + in-memory bytes.
204
+ *
205
+ * For a five-minute first run and for tests. **Everything is lost when the
206
+ * process exits** -- there is no file on disk and no bucket. Production is
207
+ * `new Filelayer(pgPool, new S3Storage({...}), { baseUrl })`; see
208
+ * docs/QUICKSTART.md, which does not hide the three configuration steps.
209
+ */
210
+ static quickstart(opts?: {
211
+ baseUrl?: string;
212
+ }): Promise<Filelayer>;
213
+ /** Where public URLs are rooted. Read-only; set once at construction. */
214
+ get baseUrl(): string | undefined;
215
+ /** The project this instance is bound to. Null means unscoped. */
216
+ get projectId(): string | null;
217
+ get files(): FilesApi;
218
+ get orgs(): OrgsApi;
219
+ get shares(): SharesApi;
220
+ /**
221
+ * Create an organization, optionally with its first owner.
222
+ *
223
+ * Creating a tenant is a control-plane operation: there is no principal
224
+ * inside the system yet who could be authorized to do it, and pretending
225
+ * otherwise would be theatre. Passing `ownerActorId` closes the bootstrap
226
+ * gap that would otherwise exist -- an org is never memberless, so there is
227
+ * never a "the org has no members yet, let anyone in" path for an attacker to
228
+ * find. Every subsequent membership change is authorized (see `addMember`).
229
+ */
230
+ createOrg(externalId: string, name?: string, opts?: {
231
+ ownerActorId?: string;
232
+ }): Promise<{
233
+ id: string;
234
+ }>;
235
+ createActor(externalId: string): Promise<{
236
+ id: string;
237
+ }>;
238
+ /**
239
+ * Register a customer application. Control plane; see the note on the
240
+ * lifecycle methods below for why these three take no principal.
241
+ */
242
+ createProject(key: string, name?: string): Promise<{
243
+ id: string;
244
+ }>;
245
+ /**
246
+ * Soft-delete a tenant. Every grant on every file in it is dead on the next
247
+ * request; every membership stops conferring anything. Nothing is erased and
248
+ * no row a retention hold protects is touched, so this cannot be used to
249
+ * defeat retention -- see SEMANTICS.md.
250
+ */
251
+ softDeleteOrg(orgId: string): Promise<void>;
252
+ /** Exactly reverses `softDeleteOrg`. Liveness is derived, so nothing is lost. */
253
+ restoreOrg(orgId: string): Promise<void>;
254
+ /**
255
+ * Soft-delete an identity.
256
+ *
257
+ * Three things die at once, and all three are derived rather than written:
258
+ * their role-derived access, every grant issued TO them, and every grant they
259
+ * ISSUED. The third is the judgement call; the reasoning is in schema.sql at
260
+ * `grant_scope_is_live` and in SEMANTICS.md. It is loud on purpose: deleting
261
+ * a prolific sharer revokes a lot of links, and that is the correct reading of
262
+ * P4, not a side effect.
263
+ */
264
+ softDeleteActor(actorId: string): Promise<void>;
265
+ restoreActor(actorId: string): Promise<void>;
266
+ /**
267
+ * Soft-delete a customer application: every org, every file and every grant
268
+ * inside it stops working immediately. This is the "we terminated that
269
+ * customer" operation and it is the widest blast radius in the system.
270
+ */
271
+ softDeleteProject(projectId: string): Promise<void>;
272
+ restoreProject(projectId: string): Promise<void>;
273
+ /**
274
+ * Add a member, or change an existing member's role.
275
+ *
276
+ * This used to take no principal at all. Anyone who could reach it could
277
+ * make anyone an owner of any org, and nothing was written to the audit log.
278
+ * Membership is the privilege that confers every other privilege, so it is
279
+ * now authorized by the same engine as everything else and audited on every
280
+ * outcome.
281
+ */
282
+ addMember(principal: Principal, orgId: string, actorId: string, role: OrgRole): Promise<void>;
283
+ removeMember(principal: Principal, orgId: string, actorId: string): Promise<void>;
284
+ /**
285
+ * Creation is the one file operation with no file to authorize against, so
286
+ * the question is org-scoped: does this actor hold `create_file` in this org?
287
+ * That is now asked of the engine rather than answered here.
288
+ *
289
+ * SIGNATURE CHANGE. This used to take a bare
290
+ * `actorId: string` while every other method took a `Principal`. That
291
+ * inconsistency was itself the defect: at the one call site in the example the
292
+ * developer passed `b.uploaderId` -- a value out of the REQUEST BODY -- rather
293
+ * than the authenticated actor, because the parameter's type did not tell them
294
+ * which one it wanted. Files are private-by-default AND owner-readable, so
295
+ * forging `owner_id` hands the wrong person permanent read access to the
296
+ * document, silently. A `Principal` is not confusable with a request field.
297
+ */
298
+ upload(principal: Principal, orgId: string, input: UploadInput): Promise<FileRecord>;
299
+ /**
300
+ * Read a file's bytes, WITH the headers required to serve them safely.
301
+ *
302
+ * The `headers` field closes a header-handling defect. Before it, this method returned a
303
+ * `Uint8Array` and the application decided what `Content-Type`,
304
+ * `Content-Disposition`, `X-Content-Type-Options` and `Cache-Control` to put
305
+ * on the response -- three security-sensitive decisions the library was
306
+ * handing back to the developer while claiming to have removed them, and
307
+ * which the example got wrong. They are computed here now, from the file
308
+ * record, with no option to disable them. See `delivery.ts`.
309
+ *
310
+ * This path now CHARGES the download cap. See `deliver()` below for the
311
+ * semantics and the reasoning.
312
+ */
313
+ read(principal: Principal, fileId: string, opts?: {
314
+ disposition?: Disposition;
315
+ }): Promise<{
316
+ file: FileRecord;
317
+ body: Uint8Array;
318
+ headers: Record<string, string>;
319
+ grantId?: string;
320
+ /** Null when no cap binds this delivery (a role-derived read, or no cap). */
321
+ remainingDownloads: number | null;
322
+ }>;
323
+ /**
324
+ * The streaming read. Same decision, same charge, same audit -- no buffer.
325
+ *
326
+ * Returns either a `ProxyDelivery` (bytes, as a stream) or, when redirect
327
+ * delivery is configured AND this delivery is eligible, a `RedirectDelivery`
328
+ * (a 302 to a short-lived presigned URL). The mode is on the returned object
329
+ * and in the audit log; nothing about it is implicit.
330
+ */
331
+ readStream(principal: Principal, fileId: string,
332
+ /**
333
+ * `mode` defaults to 'auto', which means "apply this instance's redirect
334
+ * policy". On an instance that has not configured `redirectDelivery` -- the
335
+ * default -- that policy is "never redirect", so 'auto' and 'proxy' are the
336
+ * same thing and nothing becomes cacheable that was not before. Pass
337
+ * 'proxy' to force proxying on an instance that HAS opted in.
338
+ */
339
+ opts?: {
340
+ disposition?: Disposition;
341
+ mode?: 'proxy' | 'auto';
342
+ range?: {
343
+ start: number;
344
+ end?: number;
345
+ };
346
+ }): Promise<StreamedDelivery & {
347
+ file: FileRecord;
348
+ grantId?: string;
349
+ remainingDownloads: number | null;
350
+ }>;
351
+ /**
352
+ * Metadata without bytes, authorized exactly like `read`.
353
+ *
354
+ * This exists because `getFileRecord()` used to be public and took no
355
+ * principal -- see the note on it below. Callers that wanted a file's
356
+ * metadata had an unauthorized way to get it; now they have an authorized one.
357
+ *
358
+ * It does NOT charge the download cap, and that asymmetry is the whole point
359
+ * of the delivery-cap rule: the cap counts BYTES LEAVING, and `stat` delivers
360
+ * none.
361
+ */
362
+ stat(principal: Principal, fileId: string): Promise<FileRecord>;
363
+ /**
364
+ * Which files in this org may this principal `capability`?
365
+ *
366
+ * This is the primitive that was missing, and its absence was
367
+ * the reason the "0 authorization lines" claim survived: the example simply
368
+ * did not have a listing screen, and building one meant hand-rolling the org
369
+ * filter, the visibility rule, the owner check, the role check and the union
370
+ * over `file_grant` in application SQL.
371
+ *
372
+ * WHY IT CANNOT BE GOT WRONG.
373
+ *
374
+ * - There is no filter parameter. The signature takes a principal, an org,
375
+ * a capability, a page size and an opaque cursor. There is nothing here to
376
+ * forget to pass and nothing that widens the result set.
377
+ * - The predicate is generated from the same role table and the same
378
+ * lifecycle gate `authorize()` uses (see `listPredicate` in authz.ts), so
379
+ * the two cannot drift by editing one of them.
380
+ * - `test/listing.test.ts` asserts set equality against `authorize()` over a
381
+ * randomized corpus, on every capability, on every run.
382
+ *
383
+ * WHAT IT COSTS. One SQL query and one audit event, independent of page size.
384
+ * A per-file `authorize()` loop would be 4 round trips x N.
385
+ *
386
+ * The empty page is a valid answer: a caller with no standing sees nothing,
387
+ * and so does a caller naming an org that does not exist. Neither is an error,
388
+ * because distinguishing them would rebuild the existence oracle.
389
+ */
390
+ listFiles(principal: Principal, orgId: string, opts?: ListFilesOptions): Promise<FileListPage>;
391
+ /**
392
+ * ORDERING, and it is the mirror image of `upload()`.
393
+ *
394
+ * The metadata delete COMMITS FIRST -- the decision, the audit event the
395
+ * engine wrote for it, and the state change, all in one transaction -- and
396
+ * only then are the bytes removed. A crash in between leaves an object no row
397
+ * points at, which is an orphan and therefore a garbage-collection problem.
398
+ * The other ordering would leave a live, listable, authorizable `file` row
399
+ * whose object is gone, which is data loss.
400
+ *
401
+ * The bytes are removed OUTSIDE the transaction for the same reason they are
402
+ * written outside it: object storage cannot roll back, so including it would
403
+ * mean a rolled-back transaction had already destroyed the object.
404
+ */
405
+ delete(principal: Principal, fileId: string): Promise<void>;
406
+ /**
407
+ * COLLECT ORPHANED OBJECTS. A REQUIRED OPERATIONAL JOB.
408
+ *
409
+ * An orphan is an object in the store with no `file` row pointing at
410
+ * (provider, key). Two things produce them, both of them by design:
411
+ *
412
+ * - a crash between `storage.put()` and the metadata commit in `upload()`;
413
+ * - a crash between the metadata commit and `storage.delete()` in
414
+ * `delete()`.
415
+ *
416
+ * Neither is a correctness problem -- an orphan is unreachable, because every
417
+ * read path in the system starts from a `file` row, and keys are fresh UUIDs
418
+ * that are never reissued -- but both cost money, and an uncollected orphan
419
+ * from a delete is a compliance problem: the customer was told the bytes were
420
+ * gone.
421
+ *
422
+ * WHAT MAKES THIS SAFE. Two things, and they are both load-bearing:
423
+ *
424
+ * 1. `olderThanSeconds` (default 1 hour, minimum 60s). An object written
425
+ * seconds ago may belong to an upload whose transaction has not committed
426
+ * yet. Deleting it would turn a successful upload into permanent data
427
+ * loss -- the exact failure this whole ordering exists to avoid. The grace
428
+ * period must exceed the longest plausible upload-plus-commit.
429
+ * 2. The `file` lookup is by (storage_provider, storage_key), the pair the
430
+ * UNIQUE index is on, and it is NOT project-scoped and NOT filtered on
431
+ * `deleted_at`. A soft-deleted file whose bytes were never removed still
432
+ * has a row; this job must not race the delete path into removing bytes a
433
+ * retention hold is protecting. It only removes what NOTHING references.
434
+ *
435
+ * Control plane: it takes no principal for the same reason the other
436
+ * lifecycle operations do not (see above). `dryRun` is the default.
437
+ */
438
+ collectStorageOrphans(opts?: {
439
+ prefix?: string;
440
+ olderThanSeconds?: number;
441
+ limit?: number;
442
+ dryRun?: boolean;
443
+ }): Promise<{
444
+ scanned: number;
445
+ orphans: string[];
446
+ deleted: number;
447
+ truncated: boolean;
448
+ }>;
449
+ share(principal: Principal, fileId: string, input: ShareInput): Promise<ShareResult>;
450
+ /**
451
+ * Revoke a grant.
452
+ *
453
+ * Nothing cascades, and nothing needs to: liveness is evaluated over the
454
+ * ancestor chain, so every grant ever delegated from this one dies in the
455
+ * same instant, at any depth, with no second write to get wrong (P4).
456
+ */
457
+ revoke(principal: Principal, grantId: string): Promise<void>;
458
+ listGrants(principal: Principal, fileId: string): Promise<GrantSummary[]>;
459
+ /**
460
+ * The share-link download path.
461
+ *
462
+ * Order matters: authorize FIRST (which re-validates the grant and its whole
463
+ * ancestor chain against `live_grant` -- P4, revocation beats a live URL),
464
+ * then consume the counter atomically (P6). Consuming before authorizing
465
+ * would let a revoked link burn a download; authorizing without consuming
466
+ * would make the cap a suggestion.
467
+ */
468
+ redeem(linkSecret: string, opts?: {
469
+ password?: string;
470
+ ip?: string;
471
+ userAgent?: string;
472
+ disposition?: Disposition;
473
+ }): Promise<{
474
+ file: FileRecord;
475
+ body: Uint8Array;
476
+ headers: Record<string, string>;
477
+ remainingDownloads: number | null;
478
+ }>;
479
+ /** The streaming share-link path. See `redeem()` for the ordering rationale. */
480
+ redeemStream(linkSecret: string, opts?: {
481
+ password?: string;
482
+ ip?: string;
483
+ userAgent?: string;
484
+ disposition?: Disposition;
485
+ mode?: 'proxy' | 'auto';
486
+ range?: {
487
+ start: number;
488
+ end?: number;
489
+ };
490
+ }): Promise<StreamedDelivery & {
491
+ file: FileRecord;
492
+ remainingDownloads: number | null;
493
+ }>;
494
+ /** Requires `read_audit` in the org, which is admin+. Asked of the engine. */
495
+ auditLog(principal: Principal, orgId: string, filter?: {
496
+ decision?: 'allow' | 'deny';
497
+ fileId?: string;
498
+ actorId?: string;
499
+ action?: string;
500
+ limit?: number;
501
+ }): Promise<AuditRow[]>;
502
+ /**
503
+ * Verify the tamper-evidence chain for an org.
504
+ *
505
+ * Authorized, because `checked` is a count of everything that has ever
506
+ * happened in the org and an unauthenticated caller should not be able to
507
+ * measure another tenant's activity.
508
+ */
509
+ verifyAuditChain(principal: Principal, orgId: string): Promise<AuditChainResult>;
510
+ }
511
+ /**
512
+ * Listing options.
513
+ *
514
+ * Note what is absent: any way to express a WHERE clause, a raw filter, an
515
+ * "include everything" flag, or a way to name another org. Everything here
516
+ * NARROWS the authorized set; nothing widens it. That is what "fail-closed by
517
+ * construction" has to mean for a query API -- not that the default is safe,
518
+ * but that the unsafe result is not expressible.
519
+ */
520
+ export interface ListFilesOptions {
521
+ /** Which capability the caller must hold. Default `read`. */
522
+ capability?: Capability;
523
+ /** Page size. Clamped to [1, 200]. */
524
+ limit?: number;
525
+ /** Opaque keyset cursor from a previous page's `nextCursor`. */
526
+ cursor?: string | null;
527
+ }
528
+ export interface FileListPage {
529
+ files: FileRecord[];
530
+ /** Null when this is the last page. */
531
+ nextCursor: string | null;
532
+ }
533
+ /**
534
+ * Postgres array literal. The driver will not serialize a JS array into an
535
+ * enum[] parameter, and a silent misencoding here would either error loudly
536
+ * (fine) or store a single bogus capability (not fine), so the encoding is
537
+ * explicit. Enum labels are a closed set of [a-z]+ and cannot contain a
538
+ * separator, so no quoting is required -- but we assert that rather than
539
+ * assume it.
540
+ */
541
+ export declare function pgArrayLiteral(values: readonly string[]): string;
542
+ //# sourceMappingURL=filelayer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filelayer.d.ts","sourceRoot":"","sources":["../src/filelayer.ts"],"names":[],"mappings":"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"}