@nacre.work/api 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters.d.ts +101 -8
- package/dist/adapters.d.ts.map +1 -1
- package/dist/adapters.js +115 -35
- package/dist/adapters.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.js +65 -37
- package/dist/main.js.map +1 -1
- package/dist/server.d.ts +16 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +21 -13
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
package/dist/adapters.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { aclTags, buildFilter, VectorStore, type Metadata } from '@nacre.work/core';
|
|
1
|
+
import { aclTags, buildFilter, VectorStore, type CacheStore, type Metadata } from '@nacre.work/core';
|
|
2
2
|
import type { Pool } from 'pg';
|
|
3
3
|
import { type Reranker } from './rerank.js';
|
|
4
4
|
import type { Reindex, ReindexOutcome, ReindexStatus, AuditEvent, AuditQuery, AuditReader, AuditRecord, AuditSink, Documents, GrantInput, GrantRecord, Grants, Ingest, IngestOutcome, IngestRequest, Job, Jobs, Layer, LayerOutcome, DocumentView, Layers, Page, PageResult, Workspace, WorkspaceOutcome, Workspaces, SearchHit, SearchOptions, SearchService } from './server.js';
|
|
@@ -25,6 +25,16 @@ export declare class PostgresDocuments implements Documents {
|
|
|
25
25
|
*/
|
|
26
26
|
private readonly payload;
|
|
27
27
|
private readonly role?;
|
|
28
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
29
|
+
private readonly principalsCache?;
|
|
30
|
+
/**
|
|
31
|
+
* Where a `source_url` comes from, when a deployment has object storage.
|
|
32
|
+
*
|
|
33
|
+
* Absent leaves the field off the response entirely, which is what every
|
|
34
|
+
* deployment without a bucket should see: a document whose bytes are in
|
|
35
|
+
* `documents.source_ref` has nothing to link to.
|
|
36
|
+
*/
|
|
37
|
+
private readonly presign?;
|
|
28
38
|
constructor(pool: Pool,
|
|
29
39
|
/**
|
|
30
40
|
* What writes metadata into the payload of a document's points.
|
|
@@ -33,7 +43,19 @@ export declare class PostgresDocuments implements Documents {
|
|
|
33
43
|
* has no business searching, and a port that could would let a later change
|
|
34
44
|
* reach the index without a plan.
|
|
35
45
|
*/
|
|
36
|
-
payload: DocumentMetadataWriter, role?: string | undefined
|
|
46
|
+
payload: DocumentMetadataWriter, role?: string | undefined,
|
|
47
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
48
|
+
principalsCache?: PrincipalsCache | undefined,
|
|
49
|
+
/**
|
|
50
|
+
* Where a `source_url` comes from, when a deployment has object storage.
|
|
51
|
+
*
|
|
52
|
+
* Absent leaves the field off the response entirely, which is what every
|
|
53
|
+
* deployment without a bucket should see: a document whose bytes are in
|
|
54
|
+
* `documents.source_ref` has nothing to link to.
|
|
55
|
+
*/
|
|
56
|
+
presign?: {
|
|
57
|
+
url(key: string): string;
|
|
58
|
+
} | undefined);
|
|
37
59
|
/**
|
|
38
60
|
* Undefined for absent, for another organization's, and for one this caller
|
|
39
61
|
* has no grant reaching — one answer for all three.
|
|
@@ -72,6 +94,8 @@ export interface DocumentMetadataWriter {
|
|
|
72
94
|
}
|
|
73
95
|
export interface SearchDeps {
|
|
74
96
|
readonly pool: Pool;
|
|
97
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
98
|
+
readonly principalsCache?: PrincipalsCache;
|
|
75
99
|
readonly vectors: VectorStore;
|
|
76
100
|
/**
|
|
77
101
|
* The query embedder for a given provider.
|
|
@@ -232,14 +256,29 @@ export { aclTags };
|
|
|
232
256
|
*/
|
|
233
257
|
export interface IngestDeps {
|
|
234
258
|
readonly pool: Pool;
|
|
259
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
260
|
+
readonly principalsCache?: PrincipalsCache;
|
|
235
261
|
/**
|
|
236
262
|
* What takes a document out of results. Narrower than `VectorStore` on
|
|
237
263
|
* purpose: the delete path has no business searching, and a port that could
|
|
238
264
|
* would let a later change reach the index without a plan.
|
|
239
265
|
*/
|
|
240
266
|
readonly tombstone: DocumentTombstone;
|
|
267
|
+
/**
|
|
268
|
+
* Where document bytes go, when a deployment has object storage.
|
|
269
|
+
*
|
|
270
|
+
* Absent is the supported default and not a degraded mode: bytes then live in
|
|
271
|
+
* `documents.source_ref`, which is what every deployment did before this. The
|
|
272
|
+
* difference is what a Postgres dump weighs — with this set it holds
|
|
273
|
+
* references, without it, every document in full.
|
|
274
|
+
*/
|
|
275
|
+
readonly objects?: ObjectStore;
|
|
241
276
|
readonly role?: string;
|
|
242
277
|
}
|
|
278
|
+
export interface ObjectStore {
|
|
279
|
+
put(key: string, body: Uint8Array, contentType?: string): Promise<void>;
|
|
280
|
+
remove(key: string): Promise<void>;
|
|
281
|
+
}
|
|
243
282
|
export interface DocumentTombstone {
|
|
244
283
|
tombstone(collection: string, documentId: string): Promise<void>;
|
|
245
284
|
}
|
|
@@ -264,9 +303,43 @@ export declare class NacreIngest implements Ingest {
|
|
|
264
303
|
export declare class PostgresJobs implements Jobs {
|
|
265
304
|
private readonly pool;
|
|
266
305
|
private readonly role?;
|
|
267
|
-
|
|
306
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
307
|
+
private readonly principalsCache?;
|
|
308
|
+
constructor(pool: Pool, role?: string | undefined,
|
|
309
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
310
|
+
principalsCache?: PrincipalsCache | undefined);
|
|
268
311
|
read(auth: AuthContext, jobId: string): Promise<Job | undefined>;
|
|
269
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Where the effective-principals cache is consulted, and the only place.
|
|
315
|
+
*
|
|
316
|
+
* `NACRE_ACL_CACHE_TTL` was validated at startup and read by nothing: the cache
|
|
317
|
+
* in `packages/core/authz/cache.ts` was written, tested and never called, so
|
|
318
|
+
* every request recomputed the transitive group closure. `docs/authz.md` went
|
|
319
|
+
* further and described the cache as the reason T11 is satisfied *more strongly*
|
|
320
|
+
* than specified — a claim about a code path that did not run.
|
|
321
|
+
*
|
|
322
|
+
* The safety of caching a permission input at all rests on one property, and it
|
|
323
|
+
* is structural rather than temporal: **the key carries
|
|
324
|
+
* `organizations.groups_version`**, which database triggers bump on every change
|
|
325
|
+
* to `groups`, `group_members` and `grants`. A revoked grant is not served
|
|
326
|
+
* stale, it is not served at all — the next request composes a different key.
|
|
327
|
+
* Checked against a running database rather than read: create a group, add a
|
|
328
|
+
* member, remove one, grant, revoke, delete the group; the version moved for
|
|
329
|
+
* every one.
|
|
330
|
+
*
|
|
331
|
+
* So the TTL is a memory bound. It is not what makes revocation take effect,
|
|
332
|
+
* and lowering it does not make anything safer.
|
|
333
|
+
*
|
|
334
|
+
* Absent cache means compute it. That is the pre-existing behaviour and it is
|
|
335
|
+
* the slow direction, never the permissive one — which is also why
|
|
336
|
+
* `cachedEffectivePrincipals` may swallow a Redis failure without touching
|
|
337
|
+
* invariant I3: the fallback computes the same answer from the same rows.
|
|
338
|
+
*/
|
|
339
|
+
export interface PrincipalsCache {
|
|
340
|
+
readonly store: CacheStore;
|
|
341
|
+
readonly ttlSeconds: number;
|
|
342
|
+
}
|
|
270
343
|
export declare class PostgresLayers implements Layers {
|
|
271
344
|
private readonly pool;
|
|
272
345
|
/**
|
|
@@ -282,6 +355,8 @@ export declare class PostgresLayers implements Layers {
|
|
|
282
355
|
*/
|
|
283
356
|
private readonly vectors;
|
|
284
357
|
private readonly role?;
|
|
358
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
359
|
+
private readonly principalsCache?;
|
|
285
360
|
constructor(pool: Pool,
|
|
286
361
|
/**
|
|
287
362
|
* Only to ask which named vectors the organization's collection has.
|
|
@@ -296,7 +371,9 @@ export declare class PostgresLayers implements Layers {
|
|
|
296
371
|
*/
|
|
297
372
|
vectors: {
|
|
298
373
|
vectorsOf(collection: string): Promise<Record<string, number>>;
|
|
299
|
-
}, role?: string | undefined
|
|
374
|
+
}, role?: string | undefined,
|
|
375
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
376
|
+
principalsCache?: PrincipalsCache | undefined);
|
|
300
377
|
private get scope();
|
|
301
378
|
/**
|
|
302
379
|
* The layers this caller may read.
|
|
@@ -331,7 +408,11 @@ export declare class PostgresLayers implements Layers {
|
|
|
331
408
|
export declare class PostgresWorkspaces implements Workspaces {
|
|
332
409
|
private readonly pool;
|
|
333
410
|
private readonly role?;
|
|
334
|
-
|
|
411
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
412
|
+
private readonly principalsCache?;
|
|
413
|
+
constructor(pool: Pool, role?: string | undefined,
|
|
414
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
415
|
+
principalsCache?: PrincipalsCache | undefined);
|
|
335
416
|
private get scope();
|
|
336
417
|
/**
|
|
337
418
|
* The workspaces this caller can reach, newest last, cursor-paged.
|
|
@@ -374,7 +455,11 @@ export declare class PostgresWorkspaces implements Workspaces {
|
|
|
374
455
|
export declare class PostgresGrants implements Grants {
|
|
375
456
|
private readonly pool;
|
|
376
457
|
private readonly role?;
|
|
377
|
-
|
|
458
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
459
|
+
private readonly principalsCache?;
|
|
460
|
+
constructor(pool: Pool, role?: string | undefined,
|
|
461
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
462
|
+
principalsCache?: PrincipalsCache | undefined);
|
|
378
463
|
private get scope();
|
|
379
464
|
/**
|
|
380
465
|
* Every grant in the organization, for a caller who may administer it.
|
|
@@ -440,7 +525,11 @@ export declare class PostgresGrants implements Grants {
|
|
|
440
525
|
export declare class PostgresAuditReader implements AuditReader {
|
|
441
526
|
private readonly pool;
|
|
442
527
|
private readonly role?;
|
|
443
|
-
|
|
528
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
529
|
+
private readonly principalsCache?;
|
|
530
|
+
constructor(pool: Pool, role?: string | undefined,
|
|
531
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
532
|
+
principalsCache?: PrincipalsCache | undefined);
|
|
444
533
|
/**
|
|
445
534
|
* Actions that record a substantive access to a document's contents.
|
|
446
535
|
*
|
|
@@ -488,9 +577,13 @@ export declare class PostgresReindex implements Reindex {
|
|
|
488
577
|
private readonly pool;
|
|
489
578
|
private readonly vectors;
|
|
490
579
|
private readonly role?;
|
|
580
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
581
|
+
private readonly principalsCache?;
|
|
491
582
|
constructor(pool: Pool, vectors: {
|
|
492
583
|
vectorsOf(collection: string): Promise<Record<string, number>>;
|
|
493
|
-
}, role?: string | undefined
|
|
584
|
+
}, role?: string | undefined,
|
|
585
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
586
|
+
principalsCache?: PrincipalsCache | undefined);
|
|
494
587
|
private get scope();
|
|
495
588
|
start(auth: AuthContext, layerId: string, providerId: string): Promise<ReindexOutcome | undefined>;
|
|
496
589
|
status(auth: AuthContext, layerId: string): Promise<ReindexStatus | undefined>;
|
package/dist/adapters.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,WAAW,
|
|
1
|
+
{"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,WAAW,EAaX,WAAW,EAGX,KAAK,UAAU,EAEf,KAAK,QAAQ,EAId,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,IAAI,CAAA;AAG1C,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEzD,OAAO,KAAK,EACV,OAAO,EACP,cAAc,EACd,aAAa,EACb,UAAU,EACV,UAAU,EACV,WAAW,EACX,WAAW,EACX,SAAS,EACT,SAAS,EACT,UAAU,EACV,WAAW,EACX,MAAM,EACN,MAAM,EACN,aAAa,EACb,aAAa,EACb,GAAG,EACH,IAAI,EACJ,KAAK,EACL,YAAY,EACZ,YAAY,EACZ,MAAM,EACN,IAAI,EACJ,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,aAAa,EACb,aAAa,EACd,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAE5C;;;;;;;GAOG;AAEH,MAAM,WAAW,QAAQ;IACvB,KAAK,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC,CAAA;CACzE;AAED,qBAAa,iBAAkB,YAAW,SAAS;IAE/C,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;IACtB,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;IACjC;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAnBR,IAAI,EAAE,IAAI;IAC3B;;;;;;OAMG;IACc,OAAO,EAAE,sBAAsB,EAC/B,IAAI,CAAC,EAAE,MAAM,YAAA;IAC9B,0EAA0E;IACzD,eAAe,CAAC,EAAE,eAAe,YAAA;IAClD;;;;;;OAMG;IACc,OAAO,CAAC,EAAE;QAAE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,YAAA;IAGzD;;;;;;;;;OASG;IACG,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IA4EpF;;;;;;;;;;;;;;;;;;;OAmBG;IACG,cAAc,CAClB,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,OAAO,CAAC;CAgDpB;AAED,MAAM,WAAW,sBAAsB;IACrC,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACvF;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,0EAA0E;IAC1E,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAA;IAC1C,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAA;IAC7B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,iBAAiB,KAAK,QAAQ,CAAA;IAC/D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAA;IAC5B,0DAA0D;IAC1D,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAClC;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CACnD;AAED,mDAAmD;AACnD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAC5B;AASD,qBAAa,kBAAmB,YAAW,aAAa;IAC1C,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,UAAU;IAEvC,MAAM,CACV,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC;IAyLhC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,aAAa;IA6DrB;;;;;;;;;;;;;OAaG;YACW,QAAQ;IAwBtB;;;;;;;;;;;;;;;OAeG;YACW,MAAM;IAoBpB;;;;;;;;;;;;;;;;;;;;OAoBG;YACW,OAAO;IAoDrB,8EAA8E;IAC9E,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC;CAGxE;AAED;;;;;;;GAOG;AACH,qBAAa,aAAc,YAAW,SAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAQ,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAlC,IAAI,EAAE,IAAI,EAAmB,IAAI,CAAC,EAAE,MAAM,YAAA;IAEjE,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;CAgC9C;AAED;;;;;;GAMG;AACH,qBAAa,YAAa,YAAW,QAAQ;IAEzC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,yDAAyD;IACzD,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAJT,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM;IACnC,yDAAyD;IACxC,SAAS,SAAS;IAGrC;;;;;;;OAOG;IACH,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,CAAC,QAAQ,EAAE,iBAAiB,KAAK,QAAQ;IAgBpE,KAAK,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC;CAsC/E;AAED,OAAO,EAAE,OAAO,EAAE,CAAA;AAGlB;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,0EAA0E;IAC1E,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAA;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAA;IACrC;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAA;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACnC;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACjE;AAED,qBAAa,WAAY,YAAW,MAAM;IAC5B,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,UAAU;IAE7C,OAAO,KAAK,IAAI,GAEf;IAED,OAAO,KAAK,KAAK,GAEhB;IAED,oDAAoD;YACtC,aAAa;IA4BrB,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAuFpF,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAwEtE;AAED;;;;;;;GAOG;AACH,qBAAa,YAAa,YAAW,IAAI;IAErC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;IACtB,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;gBAHhB,IAAI,EAAE,IAAI,EACV,IAAI,CAAC,EAAE,MAAM,YAAA;IAC9B,0EAA0E;IACzD,eAAe,CAAC,EAAE,eAAe,YAAA;IAG9C,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,SAAS,CAAC;CA2DvE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAC5B;AA2DD,qBAAa,cAAe,YAAW,MAAM;IAEzC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;IACtB,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;gBAfhB,IAAI,EAAE,IAAI;IAC3B;;;;;;;;;;OAUG;IACc,OAAO,EAAE;QAAE,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;KAAE,EAC3E,IAAI,CAAC,EAAE,MAAM,YAAA;IAC9B,0EAA0E;IACzD,eAAe,CAAC,EAAE,eAAe,YAAA;IAGpD,OAAO,KAAK,KAAK,GAEhB;IAED;;;;;;;OAOG;IACG,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IAgEhE,MAAM,CACV,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAC7C,OAAO,CAAC,OAAO,CAAC;IAIb,MAAM,CACV,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAC9E,OAAO,CAAC,YAAY,CAAC;CA6JzB;AAED;;;;;;;;;GASG;AACH,qBAAa,kBAAmB,YAAW,UAAU;IAEjD,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;IACtB,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;gBAHhB,IAAI,EAAE,IAAI,EACV,IAAI,CAAC,EAAE,MAAM,YAAA;IAC9B,0EAA0E;IACzD,eAAe,CAAC,EAAE,eAAe,YAAA;IAGpD,OAAO,KAAK,KAAK,GAEhB;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACG,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IA+D1E;;;;;;;;;;;;OAYG;IACG,MAAM,CACV,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GACpC,OAAO,CAAC,gBAAgB,CAAC;CAyC7B;AA8DD,qBAAa,cAAe,YAAW,MAAM;IAEzC,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;IACtB,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;gBAHhB,IAAI,EAAE,IAAI,EACV,IAAI,CAAC,EAAE,MAAM,YAAA;IAC9B,0EAA0E;IACzD,eAAe,CAAC,EAAE,eAAe,YAAA;IAGpD,OAAO,KAAK,KAAK,GAEhB;IAED;;;;;;;OAOG;IACH;;;;;;;;;;;;OAYG;IACG,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAkEtE,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAoDnF;;;;;;;;;;;;;OAaG;IACG,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAuCnE;AAGD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,mBAAoB,YAAW,WAAW;IAEnD,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;IACtB,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;gBAHhB,IAAI,EAAE,IAAI,EACV,IAAI,CAAC,EAAE,MAAM,YAAA;IAC9B,0EAA0E;IACzD,eAAe,CAAC,EAAE,eAAe,YAAA;IAGpD;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAA4D;IAE7F,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAuE9F,OAAO,CAAC,QAAQ;CAGjB;AAiBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,eAAgB,YAAW,OAAO;IAE3C,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;IACtB,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;gBAJhB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE;QAAE,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;KAAE,EAC3E,IAAI,CAAC,EAAE,MAAM,YAAA;IAC9B,0EAA0E;IACzD,eAAe,CAAC,EAAE,eAAe,YAAA;IAGpD,OAAO,KAAK,KAAK,GAEhB;IAEK,KAAK,CACT,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAkJhC,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAsCpF;;;;;;;OAOG;YACW,SAAS;IAevB,OAAO,CAAC,QAAQ;CAwBjB"}
|
package/dist/adapters.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { aclTags, buildFilter, effectivePrincipals, fromStateJson, loadGrants, loadScopeTree, PostgresGroupGraph, referenceAllows, reindexProgress, resolve, toStateJson, VectorStore, vectorName, withOrg, } from '@nacre.work/core';
|
|
1
|
+
import { aclTags, buildFilter, cachedEffectivePrincipals, documentKey, effectivePrincipals, fromStateJson, loadGrants, loadGroupsVersion, loadScopeTree, PostgresGroupGraph, referenceAllows, reindexProgress, resolve, toStateJson, VectorStore, vectorName, withOrg, } from '@nacre.work/core';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { encodeCursor, pageOf } from './pagination.js';
|
|
4
4
|
import { applyRanking } from './rerank.js';
|
|
@@ -6,6 +6,8 @@ export class PostgresDocuments {
|
|
|
6
6
|
pool;
|
|
7
7
|
payload;
|
|
8
8
|
role;
|
|
9
|
+
principalsCache;
|
|
10
|
+
presign;
|
|
9
11
|
constructor(pool,
|
|
10
12
|
/**
|
|
11
13
|
* What writes metadata into the payload of a document's points.
|
|
@@ -14,10 +16,22 @@ export class PostgresDocuments {
|
|
|
14
16
|
* has no business searching, and a port that could would let a later change
|
|
15
17
|
* reach the index without a plan.
|
|
16
18
|
*/
|
|
17
|
-
payload, role
|
|
19
|
+
payload, role,
|
|
20
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
21
|
+
principalsCache,
|
|
22
|
+
/**
|
|
23
|
+
* Where a `source_url` comes from, when a deployment has object storage.
|
|
24
|
+
*
|
|
25
|
+
* Absent leaves the field off the response entirely, which is what every
|
|
26
|
+
* deployment without a bucket should see: a document whose bytes are in
|
|
27
|
+
* `documents.source_ref` has nothing to link to.
|
|
28
|
+
*/
|
|
29
|
+
presign) {
|
|
18
30
|
this.pool = pool;
|
|
19
31
|
this.payload = payload;
|
|
20
32
|
this.role = role;
|
|
33
|
+
this.principalsCache = principalsCache;
|
|
34
|
+
this.presign = presign;
|
|
21
35
|
}
|
|
22
36
|
/**
|
|
23
37
|
* Undefined for absent, for another organization's, and for one this caller
|
|
@@ -39,11 +53,11 @@ export class PostgresDocuments {
|
|
|
39
53
|
// The plan first. Reading the row and then checking is the same
|
|
40
54
|
// information leak with extra steps if the check is ever forgotten,
|
|
41
55
|
// and the layer a document sits in is what the grant is on.
|
|
42
|
-
const plan = resolve(await contextFor(client, auth), 'read');
|
|
56
|
+
const plan = resolve(await contextFor(client, auth, this.principalsCache), 'read');
|
|
43
57
|
if (plan.kind === 'none')
|
|
44
58
|
return undefined;
|
|
45
59
|
const { rows } = await client.query(`SELECT d.id, d.title, d.layer_id, l.slug AS layer, d.status, d.chunk_count,
|
|
46
|
-
d.updated_at, d.metadata
|
|
60
|
+
d.updated_at, d.metadata, d.source_type, d.source_ref
|
|
47
61
|
FROM documents d
|
|
48
62
|
JOIN layers l ON l.id = d.layer_id AND l.org_id = d.org_id
|
|
49
63
|
WHERE d.org_id = $1 AND d.id = $2 AND d.deleted_at IS NULL`, [orgId, documentId]);
|
|
@@ -71,6 +85,17 @@ export class PostgresDocuments {
|
|
|
71
85
|
// tag has no way to tell a successful write from a dropped one — and
|
|
72
86
|
// this field was dropped by the handler for as long as it existed.
|
|
73
87
|
metadata: (row.metadata ?? {}),
|
|
88
|
+
// Minted here and nowhere earlier: everything above this line is the
|
|
89
|
+
// permission check, so a link exists only for a caller who has just
|
|
90
|
+
// been found to hold `read` on this document. `write` alone does not
|
|
91
|
+
// reach here — rule 6 — and neither does a denied document.
|
|
92
|
+
//
|
|
93
|
+
// Only for `s3`. An `inline` document has no object, and a `url` one
|
|
94
|
+
// is somebody else's address that this system has no business
|
|
95
|
+
// signing.
|
|
96
|
+
...(this.presign !== undefined && row.source_type === 's3' && row.source_ref !== null
|
|
97
|
+
? { source_url: this.presign.url(row.source_ref) }
|
|
98
|
+
: {}),
|
|
74
99
|
};
|
|
75
100
|
}, this.role === undefined ? {} : { role: this.role });
|
|
76
101
|
}
|
|
@@ -102,7 +127,7 @@ export class PostgresDocuments {
|
|
|
102
127
|
const collection = orgs[0]?.vector_collection;
|
|
103
128
|
if (collection === undefined)
|
|
104
129
|
return false;
|
|
105
|
-
const plan = resolve(await contextFor(client, auth), 'write');
|
|
130
|
+
const plan = resolve(await contextFor(client, auth, this.principalsCache), 'write');
|
|
106
131
|
if (plan.kind === 'none')
|
|
107
132
|
return false;
|
|
108
133
|
const { rows } = await client.query(`SELECT layer_id FROM documents
|
|
@@ -146,8 +171,7 @@ export class NacreSearchService {
|
|
|
146
171
|
const collection = orgs[0]?.vector_collection;
|
|
147
172
|
if (collection === undefined)
|
|
148
173
|
return undefined;
|
|
149
|
-
const
|
|
150
|
-
const principals = effectivePrincipals(auth.principal, graph);
|
|
174
|
+
const principals = await principalsFor(client, auth, this.deps.principalsCache);
|
|
151
175
|
const grants = await loadGrants(client, auth.orgId, principals);
|
|
152
176
|
const tree = await loadScopeTree(client, auth.orgId, grants.filter((g) => g.scope.type === 'document').map((g) => g.scope.id));
|
|
153
177
|
const plan = resolve({ orgId: auth.orgId, role: auth.role, principals, grants, tree }, 'read');
|
|
@@ -583,8 +607,7 @@ export class NacreIngest {
|
|
|
583
607
|
}
|
|
584
608
|
/** The layers this caller may write to, by slug. */
|
|
585
609
|
async writableLayer(client, auth, layerSlug) {
|
|
586
|
-
const
|
|
587
|
-
const principals = effectivePrincipals(auth.principal, graph);
|
|
610
|
+
const principals = await principalsFor(client, auth, this.deps.principalsCache);
|
|
588
611
|
const grants = await loadGrants(client, auth.orgId, principals);
|
|
589
612
|
const tree = await loadScopeTree(client, auth.orgId, grants.filter((g) => g.scope.type === 'document').map((g) => g.scope.id));
|
|
590
613
|
const plan = resolve({ orgId: auth.orgId, role: auth.role, principals, grants, tree }, 'write');
|
|
@@ -605,9 +628,29 @@ export class NacreIngest {
|
|
|
605
628
|
const layerId = await this.writableLayer(client, auth, request.layer);
|
|
606
629
|
if (layerId === undefined)
|
|
607
630
|
return undefined;
|
|
608
|
-
const
|
|
609
|
-
const
|
|
631
|
+
const inline = request.content !== undefined;
|
|
632
|
+
const source = inline ? request.content : request.url;
|
|
610
633
|
const hash = createHash('sha256').update(source, 'utf8').digest('hex');
|
|
634
|
+
// Bytes to object storage before the row, never after.
|
|
635
|
+
//
|
|
636
|
+
// The reverse fails unrecoverably, which is the same argument the
|
|
637
|
+
// delete path is ordered by: a row that names an object which was never
|
|
638
|
+
// written is a document the worker fails on every attempt, forever,
|
|
639
|
+
// with the API having answered `queued`. A PUT with no row is a stray
|
|
640
|
+
// object at a deterministic key that the next ingest overwrites.
|
|
641
|
+
//
|
|
642
|
+
// Only for inline content. A `url` document is a reference already, and
|
|
643
|
+
// copying somebody else's URL into our bucket at ingest time would be
|
|
644
|
+
// a fetch on the request path — which is exactly what the worker does
|
|
645
|
+
// later, with a timeout and a sandbox.
|
|
646
|
+
let sourceType = inline ? 'inline' : 'url';
|
|
647
|
+
let sourceRef = source;
|
|
648
|
+
if (inline && this.deps.objects !== undefined) {
|
|
649
|
+
const key = documentKey(auth.orgId, layerId, request.externalId);
|
|
650
|
+
await this.deps.objects.put(key, new TextEncoder().encode(source), 'text/plain');
|
|
651
|
+
sourceType = 's3';
|
|
652
|
+
sourceRef = key;
|
|
653
|
+
}
|
|
611
654
|
const { rows } = await client.query(`INSERT INTO documents
|
|
612
655
|
(org_id, layer_id, external_id, source_type, source_ref, title, content_hash, metadata, status)
|
|
613
656
|
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,'pending')
|
|
@@ -640,7 +683,7 @@ export class NacreIngest {
|
|
|
640
683
|
layerId,
|
|
641
684
|
request.externalId,
|
|
642
685
|
sourceType,
|
|
643
|
-
|
|
686
|
+
sourceRef,
|
|
644
687
|
request.title ?? null,
|
|
645
688
|
`sha256:${hash}`,
|
|
646
689
|
JSON.stringify(request.metadata),
|
|
@@ -667,8 +710,7 @@ export class NacreIngest {
|
|
|
667
710
|
const collection = orgs[0]?.vector_collection;
|
|
668
711
|
if (collection === undefined)
|
|
669
712
|
return false;
|
|
670
|
-
const
|
|
671
|
-
const principals = effectivePrincipals(auth.principal, graph);
|
|
713
|
+
const principals = await principalsFor(client, auth, this.deps.principalsCache);
|
|
672
714
|
const grants = await loadGrants(client, auth.orgId, principals);
|
|
673
715
|
const tree = await loadScopeTree(client, auth.orgId, [documentId]);
|
|
674
716
|
const plan = resolve({ orgId: auth.orgId, role: auth.role, principals, grants, tree }, 'write');
|
|
@@ -727,9 +769,13 @@ export class NacreIngest {
|
|
|
727
769
|
export class PostgresJobs {
|
|
728
770
|
pool;
|
|
729
771
|
role;
|
|
730
|
-
|
|
772
|
+
principalsCache;
|
|
773
|
+
constructor(pool, role,
|
|
774
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
775
|
+
principalsCache) {
|
|
731
776
|
this.pool = pool;
|
|
732
777
|
this.role = role;
|
|
778
|
+
this.principalsCache = principalsCache;
|
|
733
779
|
}
|
|
734
780
|
async read(auth, jobId) {
|
|
735
781
|
const orgId = auth.orgId;
|
|
@@ -740,7 +786,7 @@ export class PostgresJobs {
|
|
|
740
786
|
// different projection — and needs the same check. The status and the
|
|
741
787
|
// error string are both facts about a document the caller may not be
|
|
742
788
|
// allowed to know exists.
|
|
743
|
-
const plan = resolve(await contextFor(client, auth), 'read');
|
|
789
|
+
const plan = resolve(await contextFor(client, auth, this.principalsCache), 'read');
|
|
744
790
|
if (plan.kind === 'none')
|
|
745
791
|
return undefined;
|
|
746
792
|
const { rows } = await client.query(`SELECT id, status, error, chunk_count, layer_id FROM documents
|
|
@@ -773,10 +819,24 @@ export class PostgresJobs {
|
|
|
773
819
|
}, this.role === undefined ? {} : { role: this.role });
|
|
774
820
|
}
|
|
775
821
|
}
|
|
822
|
+
async function principalsFor(client, auth, cache) {
|
|
823
|
+
if (cache === undefined) {
|
|
824
|
+
return effectivePrincipals(auth.principal, await PostgresGroupGraph.load(client, auth.orgId));
|
|
825
|
+
}
|
|
826
|
+
// In the same transaction as the grants that follow, so the key and the grant
|
|
827
|
+
// set describe one instant. Read separately, there is a window where the
|
|
828
|
+
// version says "fresh" about a set loaded before a change.
|
|
829
|
+
const groupsVersion = await loadGroupsVersion(client, auth.orgId);
|
|
830
|
+
return cachedEffectivePrincipals({
|
|
831
|
+
orgId: auth.orgId,
|
|
832
|
+
principal: auth.principal,
|
|
833
|
+
groupsVersion,
|
|
834
|
+
ttlSeconds: cache.ttlSeconds,
|
|
835
|
+
}, cache.store, () => PostgresGroupGraph.load(client, auth.orgId));
|
|
836
|
+
}
|
|
776
837
|
/** The per-request permission context, loaded once and asked several questions. */
|
|
777
|
-
async function contextFor(client, auth) {
|
|
778
|
-
const
|
|
779
|
-
const principals = effectivePrincipals(auth.principal, graph);
|
|
838
|
+
async function contextFor(client, auth, cache) {
|
|
839
|
+
const principals = await principalsFor(client, auth, cache);
|
|
780
840
|
const grants = await loadGrants(client, auth.orgId, principals);
|
|
781
841
|
const tree = await loadScopeTree(client, auth.orgId, grants.filter((g) => g.scope.type === 'document').map((g) => g.scope.id));
|
|
782
842
|
return { orgId: auth.orgId, role: auth.role, principals, grants, tree };
|
|
@@ -785,6 +845,7 @@ export class PostgresLayers {
|
|
|
785
845
|
pool;
|
|
786
846
|
vectors;
|
|
787
847
|
role;
|
|
848
|
+
principalsCache;
|
|
788
849
|
constructor(pool,
|
|
789
850
|
/**
|
|
790
851
|
* Only to ask which named vectors the organization's collection has.
|
|
@@ -797,10 +858,13 @@ export class PostgresLayers {
|
|
|
797
858
|
* answered 202 and the row says `pending`. Checking here is what turns that
|
|
798
859
|
* into a collection rebuild instead.
|
|
799
860
|
*/
|
|
800
|
-
vectors, role
|
|
861
|
+
vectors, role,
|
|
862
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
863
|
+
principalsCache) {
|
|
801
864
|
this.pool = pool;
|
|
802
865
|
this.vectors = vectors;
|
|
803
866
|
this.role = role;
|
|
867
|
+
this.principalsCache = principalsCache;
|
|
804
868
|
}
|
|
805
869
|
get scope() {
|
|
806
870
|
return this.role === undefined ? {} : { role: this.role };
|
|
@@ -815,7 +879,7 @@ export class PostgresLayers {
|
|
|
815
879
|
*/
|
|
816
880
|
async list(auth, page) {
|
|
817
881
|
return withOrg(this.pool, auth.orgId, async (client) => {
|
|
818
|
-
const plan = resolve(await contextFor(client, auth), 'read');
|
|
882
|
+
const plan = resolve(await contextFor(client, auth, this.principalsCache), 'read');
|
|
819
883
|
if (plan.kind === 'none')
|
|
820
884
|
return { items: [], nextCursor: null };
|
|
821
885
|
// The count comes from the same statement. It is what a catalog is
|
|
@@ -861,13 +925,13 @@ export class PostgresLayers {
|
|
|
861
925
|
}, this.scope);
|
|
862
926
|
}
|
|
863
927
|
async update(auth, layerId, input) {
|
|
864
|
-
return updateLayer(this.pool, auth, layerId, input, this.role);
|
|
928
|
+
return updateLayer(this.pool, auth, layerId, input, this.role, this.principalsCache);
|
|
865
929
|
}
|
|
866
930
|
async create(auth, input) {
|
|
867
931
|
if (!/^[0-9a-f-]{36}$/i.test(input.workspaceId))
|
|
868
932
|
return { kind: 'denied' };
|
|
869
933
|
return withOrg(this.pool, auth.orgId, async (client) => {
|
|
870
|
-
const context = await contextFor(client, auth);
|
|
934
|
+
const context = await contextFor(client, auth, this.principalsCache);
|
|
871
935
|
// `referenceAllows` and not `resolve`: the question is "may they
|
|
872
936
|
// administer this one workspace", which is what the reference answers
|
|
873
937
|
// directly. Reading it out of a flattened plan would mean inferring a
|
|
@@ -994,9 +1058,13 @@ export class PostgresLayers {
|
|
|
994
1058
|
export class PostgresWorkspaces {
|
|
995
1059
|
pool;
|
|
996
1060
|
role;
|
|
997
|
-
|
|
1061
|
+
principalsCache;
|
|
1062
|
+
constructor(pool, role,
|
|
1063
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
1064
|
+
principalsCache) {
|
|
998
1065
|
this.pool = pool;
|
|
999
1066
|
this.role = role;
|
|
1067
|
+
this.principalsCache = principalsCache;
|
|
1000
1068
|
}
|
|
1001
1069
|
get scope() {
|
|
1002
1070
|
return this.role === undefined ? {} : { role: this.role };
|
|
@@ -1022,7 +1090,7 @@ export class PostgresWorkspaces {
|
|
|
1022
1090
|
*/
|
|
1023
1091
|
async list(auth, page) {
|
|
1024
1092
|
return withOrg(this.pool, auth.orgId, async (client) => {
|
|
1025
|
-
const context = await contextFor(client, auth);
|
|
1093
|
+
const context = await contextFor(client, auth, this.principalsCache);
|
|
1026
1094
|
// No early return on `plan.kind === 'none'`, and that is the whole
|
|
1027
1095
|
// point of this endpoint.
|
|
1028
1096
|
//
|
|
@@ -1114,13 +1182,13 @@ export class PostgresWorkspaces {
|
|
|
1114
1182
|
* description is built from, so an agent's picture of what a layer holds
|
|
1115
1183
|
* changes with it. That is the reason it is editable at all.
|
|
1116
1184
|
*/
|
|
1117
|
-
async function updateLayer(pool, auth, layerId, input, role) {
|
|
1185
|
+
async function updateLayer(pool, auth, layerId, input, role, principalsCache) {
|
|
1118
1186
|
if (!/^[0-9a-f-]{36}$/i.test(layerId))
|
|
1119
1187
|
return false;
|
|
1120
1188
|
if (input.name === undefined && input.description === undefined)
|
|
1121
1189
|
return false;
|
|
1122
1190
|
return withOrg(pool, auth.orgId, async (client) => {
|
|
1123
|
-
const context = await contextFor(client, auth);
|
|
1191
|
+
const context = await contextFor(client, auth, principalsCache);
|
|
1124
1192
|
// The layer's workspace, before anything is decided — the grant is on the
|
|
1125
1193
|
// workspace, so there is nothing to ask until we know which one.
|
|
1126
1194
|
const { rows } = await client.query(`SELECT workspace_id FROM layers
|
|
@@ -1147,9 +1215,13 @@ async function updateLayer(pool, auth, layerId, input, role) {
|
|
|
1147
1215
|
export class PostgresGrants {
|
|
1148
1216
|
pool;
|
|
1149
1217
|
role;
|
|
1150
|
-
|
|
1218
|
+
principalsCache;
|
|
1219
|
+
constructor(pool, role,
|
|
1220
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
1221
|
+
principalsCache) {
|
|
1151
1222
|
this.pool = pool;
|
|
1152
1223
|
this.role = role;
|
|
1224
|
+
this.principalsCache = principalsCache;
|
|
1153
1225
|
}
|
|
1154
1226
|
get scope() {
|
|
1155
1227
|
return this.role === undefined ? {} : { role: this.role };
|
|
@@ -1177,7 +1249,7 @@ export class PostgresGrants {
|
|
|
1177
1249
|
*/
|
|
1178
1250
|
async list(auth, page) {
|
|
1179
1251
|
return withOrg(this.pool, auth.orgId, async (client) => {
|
|
1180
|
-
const context = await contextFor(client, auth);
|
|
1252
|
+
const context = await contextFor(client, auth, this.principalsCache);
|
|
1181
1253
|
const plan = resolve(context, 'admin');
|
|
1182
1254
|
if (plan.kind === 'none')
|
|
1183
1255
|
return { items: [], nextCursor: null };
|
|
@@ -1215,7 +1287,7 @@ export class PostgresGrants {
|
|
|
1215
1287
|
return undefined;
|
|
1216
1288
|
}
|
|
1217
1289
|
return withOrg(this.pool, auth.orgId, async (client) => {
|
|
1218
|
-
const context = await contextFor(client, auth);
|
|
1290
|
+
const context = await contextFor(client, auth, this.principalsCache);
|
|
1219
1291
|
// Admin on the scope being granted, not admin in general. Otherwise
|
|
1220
1292
|
// anyone holding admin on one layer could grant themselves another.
|
|
1221
1293
|
if (!referenceAllows(context, { type: input.scopeType, id: input.scopeId }, 'admin')) {
|
|
@@ -1274,7 +1346,7 @@ export class PostgresGrants {
|
|
|
1274
1346
|
// because the object is a grant rather than a document.
|
|
1275
1347
|
if (row === undefined)
|
|
1276
1348
|
return false;
|
|
1277
|
-
const context = await contextFor(client, auth);
|
|
1349
|
+
const context = await contextFor(client, auth, this.principalsCache);
|
|
1278
1350
|
const scope = {
|
|
1279
1351
|
type: row.scope_type,
|
|
1280
1352
|
id: row.scope_id,
|
|
@@ -1318,9 +1390,13 @@ export class PostgresGrants {
|
|
|
1318
1390
|
export class PostgresAuditReader {
|
|
1319
1391
|
pool;
|
|
1320
1392
|
role;
|
|
1321
|
-
|
|
1393
|
+
principalsCache;
|
|
1394
|
+
constructor(pool, role,
|
|
1395
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
1396
|
+
principalsCache) {
|
|
1322
1397
|
this.pool = pool;
|
|
1323
1398
|
this.role = role;
|
|
1399
|
+
this.principalsCache = principalsCache;
|
|
1324
1400
|
}
|
|
1325
1401
|
/**
|
|
1326
1402
|
* Actions that record a substantive access to a document's contents.
|
|
@@ -1426,10 +1502,14 @@ export class PostgresReindex {
|
|
|
1426
1502
|
pool;
|
|
1427
1503
|
vectors;
|
|
1428
1504
|
role;
|
|
1429
|
-
|
|
1505
|
+
principalsCache;
|
|
1506
|
+
constructor(pool, vectors, role,
|
|
1507
|
+
/** See `principalsFor`. Absent means recompute the closure every time. */
|
|
1508
|
+
principalsCache) {
|
|
1430
1509
|
this.pool = pool;
|
|
1431
1510
|
this.vectors = vectors;
|
|
1432
1511
|
this.role = role;
|
|
1512
|
+
this.principalsCache = principalsCache;
|
|
1433
1513
|
}
|
|
1434
1514
|
get scope() {
|
|
1435
1515
|
return this.role === undefined ? {} : { role: this.role };
|
|
@@ -1440,7 +1520,7 @@ export class PostgresReindex {
|
|
|
1440
1520
|
// vector in the layer and changes which model answers every future
|
|
1441
1521
|
// query — `read` is nowhere near enough, and `write` is about putting
|
|
1442
1522
|
// documents in rather than about the layer itself.
|
|
1443
|
-
const plan = resolve(await contextFor(client, auth), 'admin');
|
|
1523
|
+
const plan = resolve(await contextFor(client, auth, this.principalsCache), 'admin');
|
|
1444
1524
|
if (plan.kind === 'none')
|
|
1445
1525
|
return undefined;
|
|
1446
1526
|
// Locked for the whole decision. Two starts arriving together would
|
|
@@ -1555,7 +1635,7 @@ export class PostgresReindex {
|
|
|
1555
1635
|
// being migrated is not an administrative fact — it explains why a
|
|
1556
1636
|
// result set moved — and hiding it from someone who can already read
|
|
1557
1637
|
// the layer buys nothing.
|
|
1558
|
-
const plan = resolve(await contextFor(client, auth), 'read');
|
|
1638
|
+
const plan = resolve(await contextFor(client, auth, this.principalsCache), 'read');
|
|
1559
1639
|
if (plan.kind === 'none')
|
|
1560
1640
|
return undefined;
|
|
1561
1641
|
const { rows } = await client.query(`SELECT id, vector_name, reindex_state FROM layers
|