@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.
@@ -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
- constructor(pool: Pool, role?: string | undefined);
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
- constructor(pool: Pool, role?: string | undefined);
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
- constructor(pool: Pool, role?: string | undefined);
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
- constructor(pool: Pool, role?: string | undefined);
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>;
@@ -1 +1 @@
1
- {"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"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"}
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 graph = await PostgresGroupGraph.load(client, auth.orgId);
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 graph = await PostgresGroupGraph.load(client, auth.orgId);
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 source = request.content !== undefined ? request.content : request.url;
609
- const sourceType = request.content !== undefined ? 'inline' : 'url';
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
- source,
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 graph = await PostgresGroupGraph.load(client, auth.orgId);
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
- constructor(pool, role) {
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 graph = await PostgresGroupGraph.load(client, auth.orgId);
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
- constructor(pool, role) {
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
- constructor(pool, role) {
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
- constructor(pool, role) {
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
- constructor(pool, vectors, role) {
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