@nacre.work/api 0.2.0 → 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.
- package/dist/adapters.d.ts +33 -5
- package/dist/adapters.d.ts.map +1 -1
- package/dist/adapters.js +199 -24
- package/dist/adapters.js.map +1 -1
- package/dist/auth.d.ts +16 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +26 -0
- package/dist/auth.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +10 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +72 -25
- package/dist/init.js.map +1 -1
- package/dist/login.d.ts +13 -1
- package/dist/login.d.ts.map +1 -1
- package/dist/login.js +6 -1
- package/dist/login.js.map +1 -1
- package/dist/main.js +39 -10
- package/dist/main.js.map +1 -1
- package/dist/pagination.d.ts +26 -2
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +11 -2
- package/dist/pagination.js.map +1 -1
- package/dist/server.d.ts +102 -37
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +368 -10
- package/dist/server.js.map +1 -1
- package/dist/service-keys.d.ts.map +1 -1
- package/dist/service-keys.js +7 -2
- package/dist/service-keys.js.map +1 -1
- package/package.json +2 -2
package/dist/adapters.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { 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
|
-
import type { Reindex, ReindexOutcome, ReindexStatus, AuditEvent, AuditQuery, AuditReader, AuditRecord,
|
|
4
|
+
import type { Reindex, ReindexOutcome, ReindexStatus, ReferenceQueries, ReferenceQuery, AuditEvent, AuditQuery, AuditReader, AuditRecord, AuditWriter, Documents, GrantInput, GrantRecord, Grants, Ingest, IngestOutcome, IngestRefused, IngestRequest, Job, Jobs, Layer, LayerOutcome, DocumentView, Layers, Page, PageResult, Workspace, WorkspaceOutcome, Workspaces, SearchHit, SearchOptions, SearchService } from './server.js';
|
|
5
5
|
import type { AuthContext } from './auth.js';
|
|
6
6
|
/**
|
|
7
7
|
* The adapters that put the permission model on the request path.
|
|
@@ -212,7 +212,7 @@ export declare class NacreSearchService implements SearchService {
|
|
|
212
212
|
* by migration 0002, so a bug here cannot rewrite history and neither can
|
|
213
213
|
* anyone holding these credentials.
|
|
214
214
|
*/
|
|
215
|
-
export declare class PostgresAudit implements
|
|
215
|
+
export declare class PostgresAudit implements AuditWriter {
|
|
216
216
|
private readonly pool;
|
|
217
217
|
private readonly role?;
|
|
218
218
|
constructor(pool: Pool, role?: string | undefined);
|
|
@@ -245,7 +245,6 @@ export declare class HttpEmbedder implements Embedder {
|
|
|
245
245
|
static pool(timeoutMs?: number): (provider: EmbeddingProvider) => Embedder;
|
|
246
246
|
embed(texts: readonly string[]): Promise<readonly (readonly number[])[]>;
|
|
247
247
|
}
|
|
248
|
-
export { aclTags };
|
|
249
248
|
/**
|
|
250
249
|
* Queueing a document, with the write permission checked first.
|
|
251
250
|
*
|
|
@@ -289,7 +288,7 @@ export declare class NacreIngest implements Ingest {
|
|
|
289
288
|
private get scope();
|
|
290
289
|
/** The layers this caller may write to, by slug. */
|
|
291
290
|
private writableLayer;
|
|
292
|
-
queue(auth: AuthContext, request: IngestRequest): Promise<IngestOutcome | undefined>;
|
|
291
|
+
queue(auth: AuthContext, request: IngestRequest): Promise<IngestOutcome | IngestRefused | undefined>;
|
|
293
292
|
remove(auth: AuthContext, documentId: string): Promise<boolean>;
|
|
294
293
|
}
|
|
295
294
|
/**
|
|
@@ -598,4 +597,33 @@ export declare class PostgresReindex implements Reindex {
|
|
|
598
597
|
private remaining;
|
|
599
598
|
private statusOf;
|
|
600
599
|
}
|
|
600
|
+
/**
|
|
601
|
+
* The reference query set behind the reindex recall gate.
|
|
602
|
+
*
|
|
603
|
+
* `admin` on the layer for both operations, and that is a decision rather than
|
|
604
|
+
* a copy of the reindex adapter above. Reading the set reveals which documents
|
|
605
|
+
* an operator considers the canonical answers to a query, which is a statement
|
|
606
|
+
* about the layer's contents; rule 7 makes `admin` the permission that implies
|
|
607
|
+
* being allowed to see them, and rule 6 means `write` does not.
|
|
608
|
+
*/
|
|
609
|
+
export declare class PostgresReferenceQueries implements ReferenceQueries {
|
|
610
|
+
private readonly pool;
|
|
611
|
+
private readonly role?;
|
|
612
|
+
private readonly principalsCache?;
|
|
613
|
+
constructor(pool: Pool, role?: string | undefined, principalsCache?: PrincipalsCache | undefined);
|
|
614
|
+
private get scope();
|
|
615
|
+
/**
|
|
616
|
+
* The layer, if this caller may administer it.
|
|
617
|
+
*
|
|
618
|
+
* `undefined` for a layer that is not there and for one they may not touch —
|
|
619
|
+
* one answer, which the handler turns into `404`. Deleted layers are not
|
|
620
|
+
* there: a reference set on a deleted layer describes nothing.
|
|
621
|
+
*/
|
|
622
|
+
private administrable;
|
|
623
|
+
list(auth: AuthContext, layerId: string): Promise<readonly ReferenceQuery[] | undefined>;
|
|
624
|
+
replace(auth: AuthContext, layerId: string, queries: readonly {
|
|
625
|
+
query: string;
|
|
626
|
+
expected: readonly string[];
|
|
627
|
+
}[]): Promise<readonly ReferenceQuery[] | undefined>;
|
|
628
|
+
}
|
|
601
629
|
//# sourceMappingURL=adapters.d.ts.map
|
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,
|
|
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 {
|
|
1
|
+
import { buildFilter, cachedEffectivePrincipals, documentKey, effectivePrincipals, fromStateJson, loadGrants, loadGroupsVersion, loadScopeTree, PostgresGroupGraph, referenceAllows, reindexProgress, activeResolver, admitIngest, 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';
|
|
@@ -53,7 +53,7 @@ export class PostgresDocuments {
|
|
|
53
53
|
// The plan first. Reading the row and then checking is the same
|
|
54
54
|
// information leak with extra steps if the check is ever forgotten,
|
|
55
55
|
// and the layer a document sits in is what the grant is on.
|
|
56
|
-
const plan = resolve(await contextFor(client, auth, this.principalsCache), 'read');
|
|
56
|
+
const plan = activeResolver().resolve(await contextFor(client, auth, this.principalsCache), 'read');
|
|
57
57
|
if (plan.kind === 'none')
|
|
58
58
|
return undefined;
|
|
59
59
|
const { rows } = await client.query(`SELECT d.id, d.title, d.layer_id, l.slug AS layer, d.status, d.chunk_count,
|
|
@@ -127,7 +127,7 @@ export class PostgresDocuments {
|
|
|
127
127
|
const collection = orgs[0]?.vector_collection;
|
|
128
128
|
if (collection === undefined)
|
|
129
129
|
return false;
|
|
130
|
-
const plan = resolve(await contextFor(client, auth, this.principalsCache), 'write');
|
|
130
|
+
const plan = activeResolver().resolve(await contextFor(client, auth, this.principalsCache), 'write');
|
|
131
131
|
if (plan.kind === 'none')
|
|
132
132
|
return false;
|
|
133
133
|
const { rows } = await client.query(`SELECT layer_id FROM documents
|
|
@@ -174,7 +174,7 @@ export class NacreSearchService {
|
|
|
174
174
|
const principals = await principalsFor(client, auth, this.deps.principalsCache);
|
|
175
175
|
const grants = await loadGrants(client, auth.orgId, principals);
|
|
176
176
|
const tree = await loadScopeTree(client, auth.orgId, grants.filter((g) => g.scope.type === 'document').map((g) => g.scope.id));
|
|
177
|
-
const plan = resolve({ orgId: auth.orgId, role: auth.role, principals, grants, tree }, 'read');
|
|
177
|
+
const plan = activeResolver().resolve({ orgId: auth.orgId, role: auth.role, principals, grants, tree }, 'read');
|
|
178
178
|
// Every live layer with the model it is on. Small — an installation
|
|
179
179
|
// runs tens of layers per organization, not thousands — and it is what
|
|
180
180
|
// decides how many branches the query has.
|
|
@@ -519,6 +519,10 @@ export class PostgresAudit {
|
|
|
519
519
|
event.requestId,
|
|
520
520
|
]);
|
|
521
521
|
}, this.role === undefined ? {} : { role: this.role });
|
|
522
|
+
// Forwarding to a module's sinks is deliberately *not* here. It is
|
|
523
|
+
// `withAuditSinks`, applied to whatever port a surface was handed, because
|
|
524
|
+
// a sink wants every recorded event and not every event this adapter
|
|
525
|
+
// happened to record.
|
|
522
526
|
}
|
|
523
527
|
}
|
|
524
528
|
/**
|
|
@@ -593,7 +597,6 @@ export class HttpEmbedder {
|
|
|
593
597
|
});
|
|
594
598
|
}
|
|
595
599
|
}
|
|
596
|
-
export { aclTags };
|
|
597
600
|
export class NacreIngest {
|
|
598
601
|
deps;
|
|
599
602
|
constructor(deps) {
|
|
@@ -610,7 +613,7 @@ export class NacreIngest {
|
|
|
610
613
|
const principals = await principalsFor(client, auth, this.deps.principalsCache);
|
|
611
614
|
const grants = await loadGrants(client, auth.orgId, principals);
|
|
612
615
|
const tree = await loadScopeTree(client, auth.orgId, grants.filter((g) => g.scope.type === 'document').map((g) => g.scope.id));
|
|
613
|
-
const plan = resolve({ orgId: auth.orgId, role: auth.role, principals, grants, tree }, 'write');
|
|
616
|
+
const plan = activeResolver().resolve({ orgId: auth.orgId, role: auth.role, principals, grants, tree }, 'write');
|
|
614
617
|
if (plan.kind === 'none')
|
|
615
618
|
return undefined;
|
|
616
619
|
const { rows } = await client.query(`SELECT id FROM layers WHERE org_id = $1 AND slug = $2 AND deleted_at IS NULL`, [auth.orgId, layerSlug]);
|
|
@@ -631,6 +634,23 @@ export class NacreIngest {
|
|
|
631
634
|
const inline = request.content !== undefined;
|
|
632
635
|
const source = inline ? request.content : request.url;
|
|
633
636
|
const hash = createHash('sha256').update(source, 'utf8').digest('hex');
|
|
637
|
+
// A module's ingest gate, after write is established and before anything
|
|
638
|
+
// is stored, so a refusal leaves neither a row nor an object behind. No
|
|
639
|
+
// gate registered is the open core admitting every document a caller may
|
|
640
|
+
// write, which is what it did before this point existed. A refusal
|
|
641
|
+
// becomes the handler's 4xx; it is not `undefined`, because that would
|
|
642
|
+
// read as "unwritable" and hide the layer the caller just wrote against.
|
|
643
|
+
const refusal = await admitIngest({
|
|
644
|
+
orgId: auth.orgId,
|
|
645
|
+
layerId,
|
|
646
|
+
principal: auth.principal,
|
|
647
|
+
role: auth.role,
|
|
648
|
+
externalId: request.externalId,
|
|
649
|
+
bytes: new TextEncoder().encode(source).byteLength,
|
|
650
|
+
});
|
|
651
|
+
if (refusal !== undefined) {
|
|
652
|
+
return { refused: true, status: refusal.status, reason: refusal.reason };
|
|
653
|
+
}
|
|
634
654
|
// Bytes to object storage before the row, never after.
|
|
635
655
|
//
|
|
636
656
|
// The reverse fails unrecoverably, which is the same argument the
|
|
@@ -713,7 +733,7 @@ export class NacreIngest {
|
|
|
713
733
|
const principals = await principalsFor(client, auth, this.deps.principalsCache);
|
|
714
734
|
const grants = await loadGrants(client, auth.orgId, principals);
|
|
715
735
|
const tree = await loadScopeTree(client, auth.orgId, [documentId]);
|
|
716
|
-
const plan = resolve({ orgId: auth.orgId, role: auth.role, principals, grants, tree }, 'write');
|
|
736
|
+
const plan = activeResolver().resolve({ orgId: auth.orgId, role: auth.role, principals, grants, tree }, 'write');
|
|
717
737
|
if (plan.kind === 'none')
|
|
718
738
|
return false;
|
|
719
739
|
const { rows } = await client.query(`SELECT layer_id FROM documents
|
|
@@ -786,7 +806,7 @@ export class PostgresJobs {
|
|
|
786
806
|
// different projection — and needs the same check. The status and the
|
|
787
807
|
// error string are both facts about a document the caller may not be
|
|
788
808
|
// allowed to know exists.
|
|
789
|
-
const plan = resolve(await contextFor(client, auth, this.principalsCache), 'read');
|
|
809
|
+
const plan = activeResolver().resolve(await contextFor(client, auth, this.principalsCache), 'read');
|
|
790
810
|
if (plan.kind === 'none')
|
|
791
811
|
return undefined;
|
|
792
812
|
const { rows } = await client.query(`SELECT id, status, error, chunk_count, layer_id FROM documents
|
|
@@ -879,7 +899,7 @@ export class PostgresLayers {
|
|
|
879
899
|
*/
|
|
880
900
|
async list(auth, page) {
|
|
881
901
|
return withOrg(this.pool, auth.orgId, async (client) => {
|
|
882
|
-
const plan = resolve(await contextFor(client, auth, this.principalsCache), 'read');
|
|
902
|
+
const plan = activeResolver().resolve(await contextFor(client, auth, this.principalsCache), 'read');
|
|
883
903
|
if (plan.kind === 'none')
|
|
884
904
|
return { items: [], nextCursor: null };
|
|
885
905
|
// The count comes from the same statement. It is what a catalog is
|
|
@@ -893,7 +913,12 @@ export class PostgresLayers {
|
|
|
893
913
|
// grants exist — a commercial module — this becomes a number that
|
|
894
914
|
// discloses more than the caller can reach, and it has to be recomputed
|
|
895
915
|
// per caller or dropped.
|
|
916
|
+
// `created_at::text` beside `created_at`, and the cursor is built from
|
|
917
|
+
// it. See `Position.createdAt` — a timestamp that has been through a
|
|
918
|
+
// JavaScript `Date` is truncated to milliseconds, and a truncated bound
|
|
919
|
+
// matches the row it came from all over again.
|
|
896
920
|
const projection = `l.id, l.slug, l.name, l.workspace_id, l.description, l.created_at,
|
|
921
|
+
l.created_at::text AS created_at_text,
|
|
897
922
|
(SELECT count(*) FROM documents d
|
|
898
923
|
WHERE d.layer_id = l.id AND d.deleted_at IS NULL) AS document_count`;
|
|
899
924
|
// Ordered by (created_at, id) rather than by slug, because that is the
|
|
@@ -921,7 +946,10 @@ export class PostgresLayers {
|
|
|
921
946
|
documentCount: Number(r.document_count),
|
|
922
947
|
createdAt: r.created_at.toISOString(),
|
|
923
948
|
}));
|
|
924
|
-
return pageOf(layers, page, (l) => ({
|
|
949
|
+
return pageOf(layers, page, (l, i) => ({
|
|
950
|
+
createdAt: rows[i].created_at_text,
|
|
951
|
+
id: l.id,
|
|
952
|
+
}));
|
|
925
953
|
}, this.scope);
|
|
926
954
|
}
|
|
927
955
|
async update(auth, layerId, input) {
|
|
@@ -1101,13 +1129,28 @@ export class PostgresWorkspaces {
|
|
|
1101
1129
|
// administer, and therefore unable to create the first layer in it,
|
|
1102
1130
|
// which is the deadlock this endpoint was added to break. It was
|
|
1103
1131
|
// written that way first and caught by running it.
|
|
1104
|
-
const plan = resolve(context, 'read');
|
|
1105
|
-
|
|
1132
|
+
const plan = activeResolver().resolve(context, 'read');
|
|
1133
|
+
// Seek and limit in SQL.
|
|
1134
|
+
//
|
|
1135
|
+
// Neither was here: the statement fetched every workspace in the
|
|
1136
|
+
// organization, ignored `page.after` entirely, and handed the whole
|
|
1137
|
+
// list to `pageOf` — which then answered "there is another page"
|
|
1138
|
+
// because the list was at least as long as the limit. So
|
|
1139
|
+
// `GET /v1/workspaces` returned the same complete list on every page
|
|
1140
|
+
// and never terminated, and a client following `next_cursor` looped
|
|
1141
|
+
// forever. Found by a test that walked the listing one item at a time.
|
|
1142
|
+
const after = page?.after;
|
|
1143
|
+
const seek = after === undefined ? '' : ' AND (w.created_at, w.id) > ($2::timestamptz, $3::uuid)';
|
|
1144
|
+
// One extra, because the filter below removes rows the caller may not
|
|
1145
|
+
// reach: without it a page whose last row is filtered out would report
|
|
1146
|
+
// itself as the end of the collection.
|
|
1147
|
+
const cap = page === undefined ? '' : ` LIMIT ${page.limit + 1}`;
|
|
1148
|
+
const { rows } = await client.query(`SELECT w.id, w.slug, w.name, w.created_at, w.created_at::text AS created_at_text,
|
|
1106
1149
|
(SELECT array_agg(l.id) FROM layers l
|
|
1107
1150
|
WHERE l.workspace_id = w.id AND l.deleted_at IS NULL) AS layer_ids
|
|
1108
1151
|
FROM workspaces w
|
|
1109
|
-
WHERE w.org_id = $1 AND w.deleted_at IS NULL
|
|
1110
|
-
ORDER BY w.created_at, w.id`, [auth.orgId]);
|
|
1152
|
+
WHERE w.org_id = $1 AND w.deleted_at IS NULL${seek}
|
|
1153
|
+
ORDER BY w.created_at, w.id${cap}`, after === undefined ? [auth.orgId] : [auth.orgId, after.createdAt, after.id]);
|
|
1111
1154
|
const reachable = rows.filter((w) => {
|
|
1112
1155
|
if (plan.kind === 'all')
|
|
1113
1156
|
return true;
|
|
@@ -1120,14 +1163,28 @@ export class PostgresWorkspaces {
|
|
|
1120
1163
|
// for a workspace with nothing in it yet.
|
|
1121
1164
|
return referenceAllows(context, { type: 'workspace', id: w.id }, 'read');
|
|
1122
1165
|
});
|
|
1123
|
-
const
|
|
1166
|
+
const fetched = page === undefined ? rows : rows.slice(0, page.limit);
|
|
1167
|
+
const items = reachable
|
|
1168
|
+
.filter((w) => fetched.some((f) => f.id === w.id))
|
|
1169
|
+
.map((w) => ({
|
|
1124
1170
|
id: w.id,
|
|
1125
1171
|
slug: w.slug,
|
|
1126
1172
|
name: w.name,
|
|
1127
1173
|
layerCount: (w.layer_ids ?? []).length,
|
|
1128
1174
|
createdAt: w.created_at.toISOString(),
|
|
1129
1175
|
}));
|
|
1130
|
-
|
|
1176
|
+
// From the last row **fetched**, not the last returned — the same rule
|
|
1177
|
+
// the grant listing follows and for the same reason: the filter above
|
|
1178
|
+
// removes rows the caller may not reach, and a cursor taken from a
|
|
1179
|
+
// survivor would skip everything the filter dropped after it.
|
|
1180
|
+
//
|
|
1181
|
+
// Which makes a short page normal here rather than a signal, and
|
|
1182
|
+
// `next_cursor` the only thing that says whether more exist.
|
|
1183
|
+
const lastFetched = fetched[fetched.length - 1];
|
|
1184
|
+
const nextCursor = page !== undefined && rows.length > page.limit && lastFetched !== undefined
|
|
1185
|
+
? encodeCursor({ createdAt: lastFetched.created_at_text, id: lastFetched.id })
|
|
1186
|
+
: null;
|
|
1187
|
+
return { items, nextCursor };
|
|
1131
1188
|
}, this.scope);
|
|
1132
1189
|
}
|
|
1133
1190
|
/**
|
|
@@ -1250,14 +1307,14 @@ export class PostgresGrants {
|
|
|
1250
1307
|
async list(auth, page) {
|
|
1251
1308
|
return withOrg(this.pool, auth.orgId, async (client) => {
|
|
1252
1309
|
const context = await contextFor(client, auth, this.principalsCache);
|
|
1253
|
-
const plan = resolve(context, 'admin');
|
|
1310
|
+
const plan = activeResolver().resolve(context, 'admin');
|
|
1254
1311
|
if (plan.kind === 'none')
|
|
1255
1312
|
return { items: [], nextCursor: null };
|
|
1256
1313
|
const after = page?.after;
|
|
1257
1314
|
const seek = after === undefined ? '' : ' AND (created_at, id) > ($2::timestamptz, $3::uuid)';
|
|
1258
1315
|
const cap = page === undefined ? '' : ` LIMIT ${page.limit}`;
|
|
1259
1316
|
const { rows } = await client.query(`SELECT id, principal_type, principal_id, scope_type, scope_id, permission, effect, source,
|
|
1260
|
-
created_at
|
|
1317
|
+
created_at, created_at::text AS created_at_text
|
|
1261
1318
|
FROM grants WHERE org_id = $1${seek} ORDER BY created_at, id${cap}`, after === undefined ? [auth.orgId] : [auth.orgId, after.createdAt, after.id]);
|
|
1262
1319
|
const visible = rows
|
|
1263
1320
|
.filter((r) => referenceAllows(context, { type: r.scope_type, id: r.scope_id }, 'admin'))
|
|
@@ -1277,7 +1334,7 @@ export class PostgresGrants {
|
|
|
1277
1334
|
// it — a page of grants silently missing from an administrator's view.
|
|
1278
1335
|
const lastFetched = rows[rows.length - 1];
|
|
1279
1336
|
const nextCursor = page !== undefined && rows.length >= page.limit && lastFetched !== undefined
|
|
1280
|
-
? encodeCursor({ createdAt: lastFetched.
|
|
1337
|
+
? encodeCursor({ createdAt: lastFetched.created_at_text, id: lastFetched.id })
|
|
1281
1338
|
: null;
|
|
1282
1339
|
return { items: visible, nextCursor };
|
|
1283
1340
|
}, this.scope);
|
|
@@ -1293,6 +1350,33 @@ export class PostgresGrants {
|
|
|
1293
1350
|
if (!referenceAllows(context, { type: input.scopeType, id: input.scopeId }, 'admin')) {
|
|
1294
1351
|
return undefined;
|
|
1295
1352
|
}
|
|
1353
|
+
// And the scope has to exist in this organization, which the check
|
|
1354
|
+
// above does not establish. `referenceAllows` returns `true` for
|
|
1355
|
+
// `org_admin` on its third line — rule 3, admin on every scope
|
|
1356
|
+
// implicitly — and it returns *before* the scope is placed, so an
|
|
1357
|
+
// administrator naming any uuid at all passed.
|
|
1358
|
+
//
|
|
1359
|
+
// Not a leak, and the reason is worth stating rather than assumed: a
|
|
1360
|
+
// grant on a scope that is not here becomes `should: layer_id = X`
|
|
1361
|
+
// beside an unconditional `must: org_id = <this tenant>`, and points
|
|
1362
|
+
// carrying layer X belong to another tenant and live in another
|
|
1363
|
+
// collection. The second line of defence holds. What got written was a
|
|
1364
|
+
// row that permits nothing and points at nothing.
|
|
1365
|
+
//
|
|
1366
|
+
// It still has to be refused. An administrator reading `/v1/grants`
|
|
1367
|
+
// sees a grant they cannot explain on an id they cannot look up, and
|
|
1368
|
+
// `404` stops meaning what invariant 4 says it means — "no permission"
|
|
1369
|
+
// and "no such object" are one answer, which requires that naming a
|
|
1370
|
+
// nonexistent object *reaches* that answer rather than succeeding.
|
|
1371
|
+
//
|
|
1372
|
+
// Written out per table rather than assembled, so the table name can
|
|
1373
|
+
// never come from a caller-supplied string. `scopeType` is a checked
|
|
1374
|
+
// union already; this makes it structural.
|
|
1375
|
+
const exists = await client.query(input.scopeType === 'workspace'
|
|
1376
|
+
? `SELECT 1 FROM workspaces WHERE org_id = $1 AND id = $2 AND deleted_at IS NULL`
|
|
1377
|
+
: `SELECT 1 FROM layers WHERE org_id = $1 AND id = $2 AND deleted_at IS NULL`, [auth.orgId, input.scopeId]);
|
|
1378
|
+
if (exists.rowCount !== 1)
|
|
1379
|
+
return undefined;
|
|
1296
1380
|
const { rows } = await client.query(`INSERT INTO grants
|
|
1297
1381
|
(org_id, principal_type, principal_id, scope_type, scope_id, permission, effect, source)
|
|
1298
1382
|
VALUES ($1,$2,$3,$4,$5,$6,'allow','api')
|
|
@@ -1446,7 +1530,8 @@ export class PostgresAuditReader {
|
|
|
1446
1530
|
// without a second query and without a count — a count over a
|
|
1447
1531
|
// retention window is the expensive thing this endpoint must not do on
|
|
1448
1532
|
// every request.
|
|
1449
|
-
const { rows } = await client.query(`SELECT id::text, occurred_at,
|
|
1533
|
+
const { rows } = await client.query(`SELECT id::text, occurred_at, occurred_at::text AS occurred_at_text,
|
|
1534
|
+
actor_type, actor_id, actor_label, action,
|
|
1450
1535
|
surface, client, target, result, detail, request_id
|
|
1451
1536
|
FROM audit_events
|
|
1452
1537
|
WHERE ${where.join(' AND ')}
|
|
@@ -1466,9 +1551,19 @@ export class PostgresAuditReader {
|
|
|
1466
1551
|
detail: r.detail ?? {},
|
|
1467
1552
|
requestId: r.request_id,
|
|
1468
1553
|
}));
|
|
1554
|
+
// From the row rather than from the mapped record, because the record
|
|
1555
|
+
// carries an ISO string and this cursor needs full precision.
|
|
1556
|
+
//
|
|
1557
|
+
// This one **skips** rather than repeats, and that is the same bug seen
|
|
1558
|
+
// from the other side: the ordering is descending, so a bound truncated
|
|
1559
|
+
// downwards excludes the row it came from *and* everything between the
|
|
1560
|
+
// truncated value and the real one. On a listing that would lose a page
|
|
1561
|
+
// of layers; on the access log it silently loses events, in the one
|
|
1562
|
+
// place the product promises a precise answer.
|
|
1563
|
+
const lastRow = rows[Math.min(page.limit, rows.length) - 1];
|
|
1469
1564
|
const last = items[items.length - 1];
|
|
1470
|
-
const nextCursor = rows.length > page.limit && last !== undefined
|
|
1471
|
-
? encodeCursor({ createdAt:
|
|
1565
|
+
const nextCursor = rows.length > page.limit && last !== undefined && lastRow !== undefined
|
|
1566
|
+
? encodeCursor({ createdAt: lastRow.occurred_at_text, id: last.id })
|
|
1472
1567
|
: null;
|
|
1473
1568
|
return { items, nextCursor };
|
|
1474
1569
|
}, this.scopeFor());
|
|
@@ -1520,7 +1615,7 @@ export class PostgresReindex {
|
|
|
1520
1615
|
// vector in the layer and changes which model answers every future
|
|
1521
1616
|
// query — `read` is nowhere near enough, and `write` is about putting
|
|
1522
1617
|
// documents in rather than about the layer itself.
|
|
1523
|
-
const plan = resolve(await contextFor(client, auth, this.principalsCache), 'admin');
|
|
1618
|
+
const plan = activeResolver().resolve(await contextFor(client, auth, this.principalsCache), 'admin');
|
|
1524
1619
|
if (plan.kind === 'none')
|
|
1525
1620
|
return undefined;
|
|
1526
1621
|
// Locked for the whole decision. Two starts arriving together would
|
|
@@ -1635,7 +1730,7 @@ export class PostgresReindex {
|
|
|
1635
1730
|
// being migrated is not an administrative fact — it explains why a
|
|
1636
1731
|
// result set moved — and hiding it from someone who can already read
|
|
1637
1732
|
// the layer buys nothing.
|
|
1638
|
-
const plan = resolve(await contextFor(client, auth, this.principalsCache), 'read');
|
|
1733
|
+
const plan = activeResolver().resolve(await contextFor(client, auth, this.principalsCache), 'read');
|
|
1639
1734
|
if (plan.kind === 'none')
|
|
1640
1735
|
return undefined;
|
|
1641
1736
|
const { rows } = await client.query(`SELECT id, vector_name, reindex_state FROM layers
|
|
@@ -1686,7 +1781,87 @@ export class PostgresReindex {
|
|
|
1686
1781
|
failed: state.failed,
|
|
1687
1782
|
progress: reindexProgress(live),
|
|
1688
1783
|
error: state.error ?? null,
|
|
1784
|
+
check: state.check ?? null,
|
|
1689
1785
|
};
|
|
1690
1786
|
}
|
|
1691
1787
|
}
|
|
1788
|
+
/**
|
|
1789
|
+
* The reference query set behind the reindex recall gate.
|
|
1790
|
+
*
|
|
1791
|
+
* `admin` on the layer for both operations, and that is a decision rather than
|
|
1792
|
+
* a copy of the reindex adapter above. Reading the set reveals which documents
|
|
1793
|
+
* an operator considers the canonical answers to a query, which is a statement
|
|
1794
|
+
* about the layer's contents; rule 7 makes `admin` the permission that implies
|
|
1795
|
+
* being allowed to see them, and rule 6 means `write` does not.
|
|
1796
|
+
*/
|
|
1797
|
+
export class PostgresReferenceQueries {
|
|
1798
|
+
pool;
|
|
1799
|
+
role;
|
|
1800
|
+
principalsCache;
|
|
1801
|
+
constructor(pool, role, principalsCache) {
|
|
1802
|
+
this.pool = pool;
|
|
1803
|
+
this.role = role;
|
|
1804
|
+
this.principalsCache = principalsCache;
|
|
1805
|
+
}
|
|
1806
|
+
get scope() {
|
|
1807
|
+
return this.role === undefined ? {} : { role: this.role };
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
* The layer, if this caller may administer it.
|
|
1811
|
+
*
|
|
1812
|
+
* `undefined` for a layer that is not there and for one they may not touch —
|
|
1813
|
+
* one answer, which the handler turns into `404`. Deleted layers are not
|
|
1814
|
+
* there: a reference set on a deleted layer describes nothing.
|
|
1815
|
+
*/
|
|
1816
|
+
async administrable(client, auth, layerId) {
|
|
1817
|
+
const plan = activeResolver().resolve(await contextFor(client, auth, this.principalsCache), 'admin');
|
|
1818
|
+
if (plan.kind === 'none')
|
|
1819
|
+
return false;
|
|
1820
|
+
if (plan.kind === 'scoped' && !plan.layers.includes(layerId))
|
|
1821
|
+
return false;
|
|
1822
|
+
const { rows } = await client.query('SELECT id FROM layers WHERE org_id = $1 AND id = $2 AND deleted_at IS NULL', [auth.orgId, layerId]);
|
|
1823
|
+
return rows.length > 0;
|
|
1824
|
+
}
|
|
1825
|
+
async list(auth, layerId) {
|
|
1826
|
+
return withOrg(this.pool, auth.orgId, async (client) => {
|
|
1827
|
+
if (!(await this.administrable(client, auth, layerId)))
|
|
1828
|
+
return undefined;
|
|
1829
|
+
const { rows } = await client.query(`SELECT id, query, expected FROM reference_queries
|
|
1830
|
+
WHERE org_id = $1 AND layer_id = $2 ORDER BY ordinal`, [auth.orgId, layerId]);
|
|
1831
|
+
return rows.map((r) => ({ id: r.id, query: r.query, expected: r.expected }));
|
|
1832
|
+
}, this.scope);
|
|
1833
|
+
}
|
|
1834
|
+
async replace(auth, layerId, queries) {
|
|
1835
|
+
return withOrg(this.pool, auth.orgId, async (client) => {
|
|
1836
|
+
if (!(await this.administrable(client, auth, layerId)))
|
|
1837
|
+
return undefined;
|
|
1838
|
+
// Delete then insert, in the transaction `withOrg` already opened. A
|
|
1839
|
+
// set is one statement about the layer, so there is no moment at which
|
|
1840
|
+
// half of the old one and half of the new one are both in the table —
|
|
1841
|
+
// which matters because the worker reads this to decide whether a layer
|
|
1842
|
+
// has a gate at all.
|
|
1843
|
+
await client.query('DELETE FROM reference_queries WHERE org_id = $1 AND layer_id = $2', [
|
|
1844
|
+
auth.orgId,
|
|
1845
|
+
layerId,
|
|
1846
|
+
]);
|
|
1847
|
+
const inserted = [];
|
|
1848
|
+
for (const [ordinal, q] of queries.entries()) {
|
|
1849
|
+
const { rows } = await client.query(`INSERT INTO reference_queries (org_id, layer_id, query, expected, ordinal)
|
|
1850
|
+
VALUES ($1, $2, $3, $4, $5) RETURNING id`, [auth.orgId, layerId, q.query, [...q.expected], ordinal]);
|
|
1851
|
+
inserted.push({
|
|
1852
|
+
id: rows[0]?.id,
|
|
1853
|
+
query: q.query,
|
|
1854
|
+
expected: [...q.expected],
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
// The external ids are deliberately **not** validated against
|
|
1858
|
+
// `documents` here. A reference set is often written before the
|
|
1859
|
+
// documents it names are ingested, and refusing it then would make the
|
|
1860
|
+
// gate impossible to set up on a new layer. The check resolves them
|
|
1861
|
+
// when it runs, and an entry that resolves to nothing fails the reindex
|
|
1862
|
+
// by name — which is the moment it actually matters.
|
|
1863
|
+
return inserted;
|
|
1864
|
+
}, this.scope);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1692
1867
|
//# sourceMappingURL=adapters.js.map
|