@nacre.work/core 0.0.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.
Files changed (89) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +9 -0
  3. package/dist/audit.d.ts +41 -0
  4. package/dist/audit.d.ts.map +1 -0
  5. package/dist/audit.js +18 -0
  6. package/dist/audit.js.map +1 -0
  7. package/dist/authz/cache.d.ts +20 -0
  8. package/dist/authz/cache.d.ts.map +1 -1
  9. package/dist/authz/cache.js +29 -0
  10. package/dist/authz/cache.js.map +1 -1
  11. package/dist/authz/filter.d.ts +54 -3
  12. package/dist/authz/filter.d.ts.map +1 -1
  13. package/dist/authz/filter.js +45 -9
  14. package/dist/authz/filter.js.map +1 -1
  15. package/dist/authz/index.d.ts +3 -3
  16. package/dist/authz/index.d.ts.map +1 -1
  17. package/dist/authz/index.js +2 -2
  18. package/dist/authz/index.js.map +1 -1
  19. package/dist/config.d.ts +55 -1
  20. package/dist/config.d.ts.map +1 -1
  21. package/dist/config.js +245 -7
  22. package/dist/config.js.map +1 -1
  23. package/dist/db/client.d.ts +39 -0
  24. package/dist/db/client.d.ts.map +1 -1
  25. package/dist/db/client.js +82 -0
  26. package/dist/db/client.js.map +1 -1
  27. package/dist/db/migrate.d.ts.map +1 -1
  28. package/dist/db/migrate.js +49 -0
  29. package/dist/db/migrate.js.map +1 -1
  30. package/dist/index.d.ts +21 -3
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +12 -3
  33. package/dist/index.js.map +1 -1
  34. package/dist/lifecycle.d.ts +57 -0
  35. package/dist/lifecycle.d.ts.map +1 -0
  36. package/dist/lifecycle.js +84 -0
  37. package/dist/lifecycle.js.map +1 -0
  38. package/dist/logging.d.ts +46 -0
  39. package/dist/logging.d.ts.map +1 -0
  40. package/dist/logging.js +110 -0
  41. package/dist/logging.js.map +1 -0
  42. package/dist/metadata.d.ts +50 -0
  43. package/dist/metadata.d.ts.map +1 -0
  44. package/dist/metadata.js +127 -0
  45. package/dist/metadata.js.map +1 -0
  46. package/dist/metrics.d.ts +19 -1
  47. package/dist/metrics.d.ts.map +1 -1
  48. package/dist/metrics.js +84 -2
  49. package/dist/metrics.js.map +1 -1
  50. package/dist/migrations/0008_authenticating_lookup.sql +124 -0
  51. package/dist/migrations/0009_refresh_tokens.sql +73 -0
  52. package/dist/migrations/0010_definer_search_path.sql +22 -0
  53. package/dist/migrations/0011_sweep_lease.sql +36 -0
  54. package/dist/migrations/0012_prune.sql +118 -0
  55. package/dist/migrations/0013_reindex.sql +65 -0
  56. package/dist/migrations/0014_retired_collections.sql +68 -0
  57. package/dist/oauth.d.ts +45 -0
  58. package/dist/oauth.d.ts.map +1 -0
  59. package/dist/oauth.js +53 -0
  60. package/dist/oauth.js.map +1 -0
  61. package/dist/observability.d.ts.map +1 -1
  62. package/dist/observability.js +100 -13
  63. package/dist/observability.js.map +1 -1
  64. package/dist/passwords.d.ts +37 -0
  65. package/dist/passwords.d.ts.map +1 -0
  66. package/dist/passwords.js +234 -0
  67. package/dist/passwords.js.map +1 -0
  68. package/dist/redis.d.ts +46 -0
  69. package/dist/redis.d.ts.map +1 -0
  70. package/dist/redis.js +234 -0
  71. package/dist/redis.js.map +1 -0
  72. package/dist/reindex.d.ts +87 -0
  73. package/dist/reindex.d.ts.map +1 -0
  74. package/dist/reindex.js +97 -0
  75. package/dist/reindex.js.map +1 -0
  76. package/dist/s3.d.ts +111 -0
  77. package/dist/s3.d.ts.map +1 -0
  78. package/dist/s3.js +242 -0
  79. package/dist/s3.js.map +1 -0
  80. package/dist/tsconfig.tsbuildinfo +1 -1
  81. package/dist/vector/query.d.ts +49 -4
  82. package/dist/vector/query.d.ts.map +1 -1
  83. package/dist/vector/query.js +43 -10
  84. package/dist/vector/query.js.map +1 -1
  85. package/dist/vector/search.d.ts +175 -4
  86. package/dist/vector/search.d.ts.map +1 -1
  87. package/dist/vector/search.js +320 -7
  88. package/dist/vector/search.js.map +1 -1
  89. package/package.json +26 -8
@@ -1,22 +1,102 @@
1
- import { type QueryablePlan } from '../authz/filter.js';
1
+ import { QdrantClient } from '@qdrant/js-client-rest';
2
+ import { type Narrowing, type QueryablePlan } from '../authz/filter.js';
2
3
  import { type Branch } from './query.js';
4
+ /**
5
+ * What Qdrant actually said.
6
+ *
7
+ * The REST client throws `Error: Bad Request` with the reason in a `data`
8
+ * property nobody looks at, so an unhandled one reaches a log as four words
9
+ * that name no collection, no vector and no cause. Both times a query in this
10
+ * repository was malformed, the message alone was useless and the reason was
11
+ * one line inside `data`.
12
+ */
13
+ export declare function explainQdrant(cause: unknown): string;
3
14
  export interface VectorStoreOptions {
4
15
  readonly url: string;
5
16
  readonly apiKey?: string;
6
17
  }
7
18
  export interface SearchRequest {
8
19
  readonly orgId: string;
9
- readonly orgSlug: string;
20
+ /**
21
+ * The organization's collection, read from `organizations.vector_collection`.
22
+ *
23
+ * Not derived from the slug. It was, everywhere, and the column was written
24
+ * once at `init` and read by nothing — so a reindex that moved the pointer
25
+ * moved a pointer no query followed, and the copy it had just finished sat
26
+ * unused while search carried on against the old collection. The name is a
27
+ * value the database owns; deriving it in six places is what let the two
28
+ * disagree silently.
29
+ */
30
+ readonly collection: string;
10
31
  /** What the caller may reach. `none` is not accepted — see buildFilter. */
11
32
  readonly plan: QueryablePlan;
12
33
  readonly branches: readonly Branch[];
13
34
  readonly topK: number;
35
+ /**
36
+ * A restriction the caller asked for, layered on top of the permission one.
37
+ *
38
+ * Still not a filter the caller assembles — it is a list of layer ids that
39
+ * `buildFilter` turns into a `must`, so it can only ever remove results. The
40
+ * distinction is the whole reason this is not `filter?: VectorFilter`.
41
+ */
42
+ readonly narrow?: Narrowing;
14
43
  }
15
44
  export interface Hit {
16
45
  readonly id: string;
17
46
  readonly score: number;
18
47
  readonly payload: Record<string, unknown>;
19
48
  }
49
+ /**
50
+ * How many metadata keys one collection will have indexes built for.
51
+ *
52
+ * The keys are the caller's, so unlike `PAYLOAD_INDEXES` the set is not known
53
+ * in advance and has no natural end: `parseMetadata` bounds a *document* to 32
54
+ * keys and says nothing about how many distinct ones an organization uses, so
55
+ * a caller putting an identifier in a key name would otherwise ask for an index
56
+ * per document.
57
+ *
58
+ * A constant rather than a `NACRE_` variable. Every configuration variable is a
59
+ * thing an operator has to understand and a thing `docs/config.md` has to
60
+ * explain, and the failure this bounds — a slower filter past the sixty-fourth
61
+ * distinct key — needs no per-deployment answer.
62
+ */
63
+ export declare const METADATA_INDEX_LIMIT = 64;
64
+ /**
65
+ * Payload indexes for the keys a caller filters on.
66
+ *
67
+ * `PAYLOAD_INDEXES` covers the fields the permission filter uses, which are
68
+ * fixed. A metadata filter reaches `meta.<key>`, and those fields had no index
69
+ * at all — so the narrowing that `filters` performs was evaluated by scanning,
70
+ * which is the failure the comment on `PAYLOAD_INDEXES` describes: it does not
71
+ * fail a test, it gets slow at a customer's volume.
72
+ *
73
+ * Built when metadata is written, because that is the only moment the keys are
74
+ * known. Three properties make that safe, and each was checked against a real
75
+ * Qdrant rather than read:
76
+ *
77
+ * - **A missing index is slow, never wrong.** A filter on an unindexed field —
78
+ * and on one indexed under the wrong type — still returns exactly the right
79
+ * points. Qdrant falls back to a scan. So this is an optimization end to end,
80
+ * and everything below may fail without changing an answer.
81
+ * - **Creating one is idempotent**, and it back-fills: an index added after the
82
+ * points covers them, so nothing has to be rebuilt or ordered.
83
+ * - **`keyword` is safe for every value type.** A number or a boolean under a
84
+ * keyword index simply indexes nothing and still matches, and strings and
85
+ * lists of strings — the case `filters` exists for, "only documents from this
86
+ * source" — index properly.
87
+ *
88
+ * Which is why a failure here warns and returns. This is the opposite of
89
+ * invariant I3 and deliberately so: nothing about an index decides who sees
90
+ * what, and failing an ingest because an index could not be built would trade a
91
+ * slow query for a document that never arrives.
92
+ */
93
+ export declare class MetadataIndexer {
94
+ #private;
95
+ constructor(client: QdrantClient, limit?: number);
96
+ ensure(collection: string, keys: Iterable<string>): Promise<void>;
97
+ /** The `meta.` indexes a collection already has, for carrying across a copy. */
98
+ static fieldsOf(client: QdrantClient, collection: string): Promise<readonly string[]>;
99
+ }
20
100
  /**
21
101
  * The vector store.
22
102
  *
@@ -32,7 +112,15 @@ export interface Hit {
32
112
  export declare class VectorStore {
33
113
  #private;
34
114
  constructor(options: VectorStoreOptions);
35
- ensureCollection(orgSlug: string, vectorName: string, size: number): Promise<void>;
115
+ /**
116
+ * Create an organization's first collection, if it is not there.
117
+ *
118
+ * `collectionName(orgSlug)` is the *initial* name and only that. Once a
119
+ * reindex has rebuilt the collection the organization's name no longer
120
+ * derives from its slug, which is why every other method here takes the
121
+ * collection rather than working it out.
122
+ */
123
+ ensureCollection(orgSlug: string, vectorName: string, size: number): Promise<string>;
36
124
  /**
37
125
  * Mark every point of a document deleted.
38
126
  *
@@ -46,7 +134,82 @@ export declare class VectorStore {
46
134
  * is tombstoned in Postgres but not here stays searchable until a background
47
135
  * job gets to it. That window is exactly what invariant I5 forbids.
48
136
  */
49
- tombstone(orgSlug: string, documentId: string): Promise<void>;
137
+ tombstone(collection: string, documentId: string): Promise<void>;
138
+ /**
139
+ * Replace the caller's metadata on every point of a document.
140
+ *
141
+ * `setPayload` under the reserved key, which merges at the top level and so
142
+ * leaves `org_id`, `deleted`, `acl_tags` and the rest exactly as they were —
143
+ * the namespace is what makes that safe rather than a list of fields to
144
+ * preserve.
145
+ *
146
+ * Whole-object replacement inside that key, not a merge: a caller sending
147
+ * `{source: "notion"}` after `{source: "confluence", team: "legal"}` means
148
+ * the document now has one tag, and merging would leave a tag they removed
149
+ * still matching filters. `PATCH` names the resource, not the field.
150
+ *
151
+ * The vectors are untouched. Re-embedding a document because a tag moved
152
+ * would make a bulk retag cost as much as a bulk ingest, which is the same
153
+ * argument the ACL retag path is built on.
154
+ */
155
+ setMetadata(collection: string, documentId: string, metadata: Record<string, unknown>): Promise<void>;
156
+ /**
157
+ * Remove a collection.
158
+ *
159
+ * Tolerates one that is not there. The caller is the retire sweep, which
160
+ * drops before it forgets the row — so a pass that dropped and then failed to
161
+ * forget comes back to a name whose collection is already gone, and that is a
162
+ * completed job rather than an error. The alternative order loses the only
163
+ * record that the collection exists.
164
+ *
165
+ * There is no confirmation and no dry run because there is no ambiguity at
166
+ * this level: by the time a name reaches here it has been through the
167
+ * rollback window and been checked against every organization's pointer.
168
+ */
169
+ dropCollection(collection: string): Promise<void>;
170
+ /**
171
+ * The named vectors a collection has, and their widths.
172
+ *
173
+ * The set is fixed when the collection is created. That is the fact the whole
174
+ * reindex design turns on, and it was worth checking rather than assuming:
175
+ * `update_collection` adjusts the parameters of vectors that already exist and
176
+ * answers `Not existing vector name error` for anything else, so there is no
177
+ * way to add one to a live collection.
178
+ */
179
+ vectorsOf(collection: string): Promise<Record<string, number>>;
180
+ /**
181
+ * Copy an organization's collection into a new one that also has room for a
182
+ * new model.
183
+ *
184
+ * This exists because a named vector cannot be added to a collection after it
185
+ * is created. The way around that is not to add one — it is to create a
186
+ * collection that already has the slot, move the points across as they are,
187
+ * and switch which collection the organization points at.
188
+ *
189
+ * **No embeddings are computed here.** The vectors that come out of the old
190
+ * collection go into the new one unchanged. That is what makes this the cheap
191
+ * half of a model migration: the expensive half — computing vectors with the
192
+ * new model — happens afterwards, one layer at a time, into the slot this
193
+ * created.
194
+ *
195
+ * Idempotent by construction: if the target already exists it is deleted and
196
+ * rebuilt. A half-copied collection is worse than none, and there is no way to
197
+ * tell one from a complete one by looking at it — the source is still live and
198
+ * still authoritative until the pointer moves, so throwing away a partial
199
+ * attempt costs only the time it took.
200
+ */
201
+ copyCollection(input: {
202
+ from: string;
203
+ to: string;
204
+ /** Added alongside everything the source already has. */
205
+ addVector: {
206
+ name: string;
207
+ size: number;
208
+ };
209
+ /** Points per scroll page and per upsert. */
210
+ batch?: number;
211
+ onProgress?: (copied: number) => void;
212
+ }): Promise<void>;
50
213
  search(request: SearchRequest): Promise<readonly Hit[]>;
51
214
  /**
52
215
  * Re-check the tenant on the way out.
@@ -57,6 +220,14 @@ export declare class VectorStore {
57
220
  * and a bug that produces one cross-tenant row will produce more.
58
221
  */
59
222
  static assertTenant(orgId: string, hits: readonly Hit[]): readonly Hit[];
223
+ /**
224
+ * Whether the vector store is answering.
225
+ *
226
+ * `getCollections` rather than a bare TCP connect: Qdrant accepting a socket
227
+ * while refusing queries is the failure a readiness probe exists to catch,
228
+ * and the Compose healthcheck already covers the socket.
229
+ */
230
+ ready(): Promise<boolean>;
60
231
  close(): Promise<void>;
61
232
  }
62
233
  //# sourceMappingURL=search.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../vector/search.ts"],"names":[],"mappings":"AAEA,OAAO,EAAe,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACpE,OAAO,EAAuE,KAAK,MAAM,EAAE,MAAM,YAAY,CAAA;AAE7G,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,GAAG;IAClB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC1C;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,WAAW;;gBAGV,OAAO,EAAE,kBAAkB;IAWjC,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBxF;;;;;;;;;;;;OAYG;IACG,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ7D,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,SAAS,GAAG,EAAE,CAAC;IAiB7D;;;;;;;OAOG;IACH,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,EAAE,GAAG,SAAS,GAAG,EAAE;IAYlE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAI7B"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../vector/search.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,132 @@
1
1
  import { QdrantClient } from '@qdrant/js-client-rest';
2
- import { buildFilter } from '../authz/filter.js';
3
- import { buildHybridQuery, collectionConfig, collectionName, PAYLOAD_INDEXES } from './query.js';
2
+ import { buildFilter, METADATA_PREFIX } from '../authz/filter.js';
3
+ import { logger } from '../logging.js';
4
+ import { buildHybridQuery, collectionConfig, collectionName, PAYLOAD_INDEXES, vectorParams } from './query.js';
5
+ /**
6
+ * What Qdrant actually said.
7
+ *
8
+ * The REST client throws `Error: Bad Request` with the reason in a `data`
9
+ * property nobody looks at, so an unhandled one reaches a log as four words
10
+ * that name no collection, no vector and no cause. Both times a query in this
11
+ * repository was malformed, the message alone was useless and the reason was
12
+ * one line inside `data`.
13
+ */
14
+ export function explainQdrant(cause) {
15
+ const data = cause?.data;
16
+ return data === undefined ? String(cause) : `${String(cause)} — ${JSON.stringify(data)}`;
17
+ }
18
+ /**
19
+ * How many metadata keys one collection will have indexes built for.
20
+ *
21
+ * The keys are the caller's, so unlike `PAYLOAD_INDEXES` the set is not known
22
+ * in advance and has no natural end: `parseMetadata` bounds a *document* to 32
23
+ * keys and says nothing about how many distinct ones an organization uses, so
24
+ * a caller putting an identifier in a key name would otherwise ask for an index
25
+ * per document.
26
+ *
27
+ * A constant rather than a `NACRE_` variable. Every configuration variable is a
28
+ * thing an operator has to understand and a thing `docs/config.md` has to
29
+ * explain, and the failure this bounds — a slower filter past the sixty-fourth
30
+ * distinct key — needs no per-deployment answer.
31
+ */
32
+ export const METADATA_INDEX_LIMIT = 64;
33
+ /**
34
+ * Payload indexes for the keys a caller filters on.
35
+ *
36
+ * `PAYLOAD_INDEXES` covers the fields the permission filter uses, which are
37
+ * fixed. A metadata filter reaches `meta.<key>`, and those fields had no index
38
+ * at all — so the narrowing that `filters` performs was evaluated by scanning,
39
+ * which is the failure the comment on `PAYLOAD_INDEXES` describes: it does not
40
+ * fail a test, it gets slow at a customer's volume.
41
+ *
42
+ * Built when metadata is written, because that is the only moment the keys are
43
+ * known. Three properties make that safe, and each was checked against a real
44
+ * Qdrant rather than read:
45
+ *
46
+ * - **A missing index is slow, never wrong.** A filter on an unindexed field —
47
+ * and on one indexed under the wrong type — still returns exactly the right
48
+ * points. Qdrant falls back to a scan. So this is an optimization end to end,
49
+ * and everything below may fail without changing an answer.
50
+ * - **Creating one is idempotent**, and it back-fills: an index added after the
51
+ * points covers them, so nothing has to be rebuilt or ordered.
52
+ * - **`keyword` is safe for every value type.** A number or a boolean under a
53
+ * keyword index simply indexes nothing and still matches, and strings and
54
+ * lists of strings — the case `filters` exists for, "only documents from this
55
+ * source" — index properly.
56
+ *
57
+ * Which is why a failure here warns and returns. This is the opposite of
58
+ * invariant I3 and deliberately so: nothing about an index decides who sees
59
+ * what, and failing an ingest because an index could not be built would trade a
60
+ * slow query for a document that never arrives.
61
+ */
62
+ export class MetadataIndexer {
63
+ #client;
64
+ #limit;
65
+ /** Per collection, the `meta.` fields known to be indexed. */
66
+ #known = new Map();
67
+ #atLimit = new Set();
68
+ constructor(client, limit = METADATA_INDEX_LIMIT) {
69
+ this.#client = client;
70
+ this.#limit = limit;
71
+ }
72
+ /**
73
+ * Held on the instance rather than in a module-level map: two stores against
74
+ * different collections must not share it, and a cache that outlives the
75
+ * process that built it is a cache nothing invalidates. Staleness is harmless
76
+ * anyway — the operation it saves is idempotent.
77
+ */
78
+ async #indexed(collection) {
79
+ const cached = this.#known.get(collection);
80
+ if (cached !== undefined)
81
+ return cached;
82
+ const info = await this.#client.getCollection(collection);
83
+ const schema = (info.payload_schema ?? {});
84
+ const fields = new Set(Object.keys(schema).filter((field) => field.startsWith(`${METADATA_PREFIX}.`)));
85
+ this.#known.set(collection, fields);
86
+ return fields;
87
+ }
88
+ async ensure(collection, keys) {
89
+ const wanted = [...keys];
90
+ if (wanted.length === 0)
91
+ return;
92
+ try {
93
+ const indexed = await this.#indexed(collection);
94
+ for (const key of wanted) {
95
+ const field = `${METADATA_PREFIX}.${key}`;
96
+ if (indexed.has(field))
97
+ continue;
98
+ if (indexed.size >= this.#limit) {
99
+ // Once per collection per process. The filter still answers
100
+ // correctly; it stops being answered from an index.
101
+ if (!this.#atLimit.has(collection)) {
102
+ this.#atLimit.add(collection);
103
+ logger.warn('metadata index limit reached', { collection,
104
+ limit: this.#limit,
105
+ unindexed: field });
106
+ }
107
+ continue;
108
+ }
109
+ await this.#client.createPayloadIndex(collection, {
110
+ field_name: field,
111
+ field_schema: 'keyword',
112
+ wait: true,
113
+ });
114
+ indexed.add(field);
115
+ }
116
+ }
117
+ catch (cause) {
118
+ // Never fatal. See the note on this class.
119
+ logger.warn('metadata index build failed', { collection,
120
+ error: explainQdrant(cause) });
121
+ }
122
+ }
123
+ /** The `meta.` indexes a collection already has, for carrying across a copy. */
124
+ static async fieldsOf(client, collection) {
125
+ const info = await client.getCollection(collection);
126
+ const schema = (info.payload_schema ?? {});
127
+ return Object.keys(schema).filter((field) => field.startsWith(`${METADATA_PREFIX}.`));
128
+ }
129
+ }
4
130
  /**
5
131
  * The vector store.
6
132
  *
@@ -15,6 +141,7 @@ import { buildHybridQuery, collectionConfig, collectionName, PAYLOAD_INDEXES } f
15
141
  */
16
142
  export class VectorStore {
17
143
  #client;
144
+ #metadataIndexes;
18
145
  constructor(options) {
19
146
  // exactOptionalPropertyTypes distinguishes "absent" from "present and
20
147
  // undefined", and the client's parameters mean it: passing apiKey:
@@ -22,12 +149,21 @@ export class VectorStore {
22
149
  this.#client = new QdrantClient(options.apiKey === undefined
23
150
  ? { url: options.url }
24
151
  : { url: options.url, apiKey: options.apiKey });
152
+ this.#metadataIndexes = new MetadataIndexer(this.#client);
25
153
  }
154
+ /**
155
+ * Create an organization's first collection, if it is not there.
156
+ *
157
+ * `collectionName(orgSlug)` is the *initial* name and only that. Once a
158
+ * reindex has rebuilt the collection the organization's name no longer
159
+ * derives from its slug, which is why every other method here takes the
160
+ * collection rather than working it out.
161
+ */
26
162
  async ensureCollection(orgSlug, vectorName, size) {
27
163
  const name = collectionName(orgSlug);
28
164
  const existing = await this.#client.getCollections();
29
165
  if (existing.collections.some((c) => c.name === name))
30
- return;
166
+ return name;
31
167
  await this.#client.createCollection(name, collectionConfig(vectorName, size));
32
168
  // Created here rather than left to an operator: a filter that falls back to
33
169
  // a scan is a latency problem that only shows up at a customer's volume.
@@ -38,6 +174,7 @@ export class VectorStore {
38
174
  wait: true,
39
175
  });
40
176
  }
177
+ return name;
41
178
  }
42
179
  /**
43
180
  * Mark every point of a document deleted.
@@ -52,21 +189,186 @@ export class VectorStore {
52
189
  * is tombstoned in Postgres but not here stays searchable until a background
53
190
  * job gets to it. That window is exactly what invariant I5 forbids.
54
191
  */
55
- async tombstone(orgSlug, documentId) {
56
- await this.#client.setPayload(collectionName(orgSlug), {
192
+ async tombstone(collection, documentId) {
193
+ await this.#client.setPayload(collection, {
57
194
  wait: true,
58
195
  payload: { deleted: true },
59
196
  filter: { must: [{ key: 'doc_id', match: { value: documentId } }] },
60
197
  });
61
198
  }
199
+ /**
200
+ * Replace the caller's metadata on every point of a document.
201
+ *
202
+ * `setPayload` under the reserved key, which merges at the top level and so
203
+ * leaves `org_id`, `deleted`, `acl_tags` and the rest exactly as they were —
204
+ * the namespace is what makes that safe rather than a list of fields to
205
+ * preserve.
206
+ *
207
+ * Whole-object replacement inside that key, not a merge: a caller sending
208
+ * `{source: "notion"}` after `{source: "confluence", team: "legal"}` means
209
+ * the document now has one tag, and merging would leave a tag they removed
210
+ * still matching filters. `PATCH` names the resource, not the field.
211
+ *
212
+ * The vectors are untouched. Re-embedding a document because a tag moved
213
+ * would make a bulk retag cost as much as a bulk ingest, which is the same
214
+ * argument the ACL retag path is built on.
215
+ */
216
+ async setMetadata(collection, documentId, metadata) {
217
+ try {
218
+ await this.#client.setPayload(collection, {
219
+ wait: true,
220
+ payload: { [METADATA_PREFIX]: metadata },
221
+ filter: { must: [{ key: 'doc_id', match: { value: documentId } }] },
222
+ });
223
+ }
224
+ catch (cause) {
225
+ throw new Error(`metadata write to ${collection} rejected: ${explainQdrant(cause)}`, { cause });
226
+ }
227
+ // After the write, not before: an index built for a write that then failed
228
+ // is an index for a key no point carries.
229
+ await this.#metadataIndexes.ensure(collection, Object.keys(metadata));
230
+ }
231
+ /**
232
+ * Remove a collection.
233
+ *
234
+ * Tolerates one that is not there. The caller is the retire sweep, which
235
+ * drops before it forgets the row — so a pass that dropped and then failed to
236
+ * forget comes back to a name whose collection is already gone, and that is a
237
+ * completed job rather than an error. The alternative order loses the only
238
+ * record that the collection exists.
239
+ *
240
+ * There is no confirmation and no dry run because there is no ambiguity at
241
+ * this level: by the time a name reaches here it has been through the
242
+ * rollback window and been checked against every organization's pointer.
243
+ */
244
+ async dropCollection(collection) {
245
+ try {
246
+ await this.#client.deleteCollection(collection);
247
+ }
248
+ catch (cause) {
249
+ const existing = await this.#client.getCollections();
250
+ if (existing.collections.some((c) => c.name === collection)) {
251
+ throw new Error(`deleting ${collection} rejected: ${explainQdrant(cause)}`, { cause });
252
+ }
253
+ }
254
+ }
255
+ /**
256
+ * The named vectors a collection has, and their widths.
257
+ *
258
+ * The set is fixed when the collection is created. That is the fact the whole
259
+ * reindex design turns on, and it was worth checking rather than assuming:
260
+ * `update_collection` adjusts the parameters of vectors that already exist and
261
+ * answers `Not existing vector name error` for anything else, so there is no
262
+ * way to add one to a live collection.
263
+ */
264
+ async vectorsOf(collection) {
265
+ const info = await this.#client.getCollection(collection);
266
+ const vectors = (info.config?.params?.vectors ?? {});
267
+ return Object.fromEntries(Object.entries(vectors).map(([name, v]) => [name, Number(v.size ?? 0)]));
268
+ }
269
+ /**
270
+ * Copy an organization's collection into a new one that also has room for a
271
+ * new model.
272
+ *
273
+ * This exists because a named vector cannot be added to a collection after it
274
+ * is created. The way around that is not to add one — it is to create a
275
+ * collection that already has the slot, move the points across as they are,
276
+ * and switch which collection the organization points at.
277
+ *
278
+ * **No embeddings are computed here.** The vectors that come out of the old
279
+ * collection go into the new one unchanged. That is what makes this the cheap
280
+ * half of a model migration: the expensive half — computing vectors with the
281
+ * new model — happens afterwards, one layer at a time, into the slot this
282
+ * created.
283
+ *
284
+ * Idempotent by construction: if the target already exists it is deleted and
285
+ * rebuilt. A half-copied collection is worse than none, and there is no way to
286
+ * tell one from a complete one by looking at it — the source is still live and
287
+ * still authoritative until the pointer moves, so throwing away a partial
288
+ * attempt costs only the time it took.
289
+ */
290
+ async copyCollection(input) {
291
+ const existing = await this.vectorsOf(input.from);
292
+ if (input.addVector.name in existing) {
293
+ throw new Error(`${input.from} already has a vector called ${input.addVector.name}; nothing to copy for`);
294
+ }
295
+ const vectors = {};
296
+ for (const [name, size] of Object.entries(existing)) {
297
+ vectors[name] = vectorParams(size);
298
+ }
299
+ vectors[input.addVector.name] = vectorParams(input.addVector.size);
300
+ // Rebuilt rather than resumed. See the note above.
301
+ await this.#client.deleteCollection(input.to).catch(() => undefined);
302
+ await this.#client.createCollection(input.to, {
303
+ vectors: vectors,
304
+ sparse_vectors: { bm25: {} },
305
+ optimizers_config: { default_segment_number: 4 },
306
+ on_disk_payload: true,
307
+ });
308
+ for (const index of PAYLOAD_INDEXES) {
309
+ await this.#client.createPayloadIndex(input.to, {
310
+ field_name: index.field_name,
311
+ field_schema: index.field_schema,
312
+ wait: true,
313
+ });
314
+ }
315
+ // The metadata indexes too, or a reindex quietly undoes them: the new
316
+ // collection is built from `PAYLOAD_INDEXES`, which by construction knows
317
+ // nothing about keys a caller chose, so every metadata filter in that
318
+ // organization would go back to scanning the moment the pointer moved. The
319
+ // source is the only record of which keys are in use.
320
+ for (const field of await MetadataIndexer.fieldsOf(this.#client, input.from)) {
321
+ await this.#client.createPayloadIndex(input.to, {
322
+ field_name: field,
323
+ field_schema: 'keyword',
324
+ wait: true,
325
+ });
326
+ }
327
+ const batch = input.batch ?? 256;
328
+ let offset = undefined;
329
+ let copied = 0;
330
+ do {
331
+ const page = await this.#client.scroll(input.from, {
332
+ limit: batch,
333
+ with_payload: true,
334
+ with_vector: true,
335
+ ...(offset === undefined || offset === null ? {} : { offset }),
336
+ });
337
+ const points = page.points ?? [];
338
+ if (points.length > 0) {
339
+ await this.#client.upsert(input.to, {
340
+ wait: true,
341
+ points: points.map((p) => ({
342
+ id: p.id,
343
+ // Whatever the source had, unchanged. The new slot stays empty
344
+ // until a layer is reindexed into it.
345
+ vector: p.vector,
346
+ payload: p.payload ?? {},
347
+ })),
348
+ });
349
+ copied += points.length;
350
+ input.onProgress?.(copied);
351
+ }
352
+ offset = page.next_page_offset;
353
+ } while (offset !== undefined && offset !== null);
354
+ }
62
355
  async search(request) {
63
- const filter = buildFilter(request.orgId, request.plan);
356
+ const filter = buildFilter(request.orgId, request.plan, request.narrow);
64
357
  const query = buildHybridQuery({
65
358
  branches: request.branches,
66
359
  filter,
67
360
  limit: request.topK,
68
361
  });
69
- const result = await this.#client.query(collectionName(request.orgSlug), query);
362
+ let result;
363
+ try {
364
+ result = await this.#client.query(request.collection, query);
365
+ }
366
+ catch (cause) {
367
+ // Named, because the query has as many branches as the organization has
368
+ // models and "Bad Request" does not say which one Qdrant objected to.
369
+ throw new Error(`query against ${request.collection} using ` +
370
+ `${query.prefetch.map((b) => b.using).join(', ')} rejected: ${explainQdrant(cause)}`, { cause });
371
+ }
70
372
  return result.points.map((p) => ({
71
373
  id: String(p.id),
72
374
  score: p.score,
@@ -90,6 +392,17 @@ export class VectorStore {
90
392
  }
91
393
  return hits;
92
394
  }
395
+ /**
396
+ * Whether the vector store is answering.
397
+ *
398
+ * `getCollections` rather than a bare TCP connect: Qdrant accepting a socket
399
+ * while refusing queries is the failure a readiness probe exists to catch,
400
+ * and the Compose healthcheck already covers the socket.
401
+ */
402
+ async ready() {
403
+ await this.#client.getCollections();
404
+ return true;
405
+ }
93
406
  async close() {
94
407
  // The REST client holds no pool; this exists so callers have one shape to
95
408
  // depend on when it does.
@@ -1 +1 @@
1
- {"version":3,"file":"search.js","sourceRoot":"","sources":["../../vector/search.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../vector/search.ts"],"names":[],"mappings":"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"}