@nacre.work/core 0.0.0 → 0.1.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/LICENSE +202 -0
- package/NOTICE +9 -0
- package/dist/authz/filter.d.ts +54 -3
- package/dist/authz/filter.d.ts.map +1 -1
- package/dist/authz/filter.js +45 -9
- package/dist/authz/filter.js.map +1 -1
- package/dist/authz/index.d.ts +2 -2
- package/dist/authz/index.d.ts.map +1 -1
- package/dist/authz/index.js +1 -1
- package/dist/authz/index.js.map +1 -1
- package/dist/config.d.ts +33 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +137 -3
- package/dist/config.js.map +1 -1
- package/dist/db/client.d.ts +39 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +82 -0
- package/dist/db/client.js.map +1 -1
- package/dist/db/migrate.d.ts.map +1 -1
- package/dist/db/migrate.js +49 -0
- package/dist/db/migrate.js.map +1 -1
- package/dist/index.d.ts +15 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/lifecycle.d.ts +57 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +97 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/metadata.d.ts +50 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +127 -0
- package/dist/metadata.js.map +1 -0
- package/dist/metrics.d.ts +18 -1
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +77 -2
- package/dist/metrics.js.map +1 -1
- package/dist/migrations/0008_authenticating_lookup.sql +124 -0
- package/dist/migrations/0009_refresh_tokens.sql +73 -0
- package/dist/migrations/0010_definer_search_path.sql +22 -0
- package/dist/migrations/0011_sweep_lease.sql +36 -0
- package/dist/migrations/0012_prune.sql +118 -0
- package/dist/migrations/0013_reindex.sql +65 -0
- package/dist/oauth.d.ts +45 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +53 -0
- package/dist/oauth.js.map +1 -0
- package/dist/observability.d.ts.map +1 -1
- package/dist/observability.js +81 -13
- package/dist/observability.js.map +1 -1
- package/dist/passwords.d.ts +37 -0
- package/dist/passwords.d.ts.map +1 -0
- package/dist/passwords.js +234 -0
- package/dist/passwords.js.map +1 -0
- package/dist/redis.d.ts +46 -0
- package/dist/redis.d.ts.map +1 -0
- package/dist/redis.js +234 -0
- package/dist/redis.js.map +1 -0
- package/dist/reindex.d.ts +87 -0
- package/dist/reindex.d.ts.map +1 -0
- package/dist/reindex.js +97 -0
- package/dist/reindex.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/vector/query.d.ts +49 -4
- package/dist/vector/query.d.ts.map +1 -1
- package/dist/vector/query.js +43 -10
- package/dist/vector/query.js.map +1 -1
- package/dist/vector/search.d.ts +109 -4
- package/dist/vector/search.d.ts.map +1 -1
- package/dist/vector/search.js +166 -7
- package/dist/vector/search.js.map +1 -1
- package/package.json +26 -8
package/dist/config.js
CHANGED
|
@@ -14,6 +14,45 @@
|
|
|
14
14
|
* obviously right value — and never fine for anything naming a host or
|
|
15
15
|
* carrying a credential.
|
|
16
16
|
*/
|
|
17
|
+
export function loadJwtKeys(env = process.env) {
|
|
18
|
+
// Development uses a symmetric secret. Production is meant to load an Ed25519
|
|
19
|
+
// key through NACRE_JWT_PRIVATE_KEY_REF; until that lands, refusing is the
|
|
20
|
+
// honest behaviour — a hardcoded fallback here would be a signing key anyone
|
|
21
|
+
// reading the source can forge tokens with.
|
|
22
|
+
const secret = env.NACRE_JWT_SECRET;
|
|
23
|
+
if (secret === undefined || secret.length < 32) {
|
|
24
|
+
throw new ConfigError([
|
|
25
|
+
'NACRE_JWT_SECRET is not set, or is shorter than 32 bytes. ' +
|
|
26
|
+
'Asymmetric keys through NACRE_JWT_PRIVATE_KEY_REF are not implemented yet; ' +
|
|
27
|
+
'until they are, this is required and there is no default.',
|
|
28
|
+
]);
|
|
29
|
+
}
|
|
30
|
+
const previous = env.NACRE_JWT_SECRET_PREVIOUS;
|
|
31
|
+
if (previous === undefined || previous === '') {
|
|
32
|
+
return { key: new TextEncoder().encode(secret), alsoAccept: [] };
|
|
33
|
+
}
|
|
34
|
+
if (previous.length < 32) {
|
|
35
|
+
throw new ConfigError([
|
|
36
|
+
'NACRE_JWT_SECRET_PREVIOUS is set but shorter than 32 bytes. It holds the ' +
|
|
37
|
+
'key being rotated out, so it is held to the same floor as the one ' +
|
|
38
|
+
'replacing it. Unset it to finish the rotation.',
|
|
39
|
+
]);
|
|
40
|
+
}
|
|
41
|
+
if (previous === secret) {
|
|
42
|
+
// Refused rather than deduplicated. Setting both to the same value is what
|
|
43
|
+
// an operator does when they mean to rotate and copy the wrong line, and
|
|
44
|
+
// it leaves an installation that believes it has rotated and has not.
|
|
45
|
+
throw new ConfigError([
|
|
46
|
+
'NACRE_JWT_SECRET_PREVIOUS is the same value as NACRE_JWT_SECRET. That is ' +
|
|
47
|
+
'not a rotation: it accepts one key twice. Set NACRE_JWT_SECRET to the ' +
|
|
48
|
+
'new key and NACRE_JWT_SECRET_PREVIOUS to the one it replaces.',
|
|
49
|
+
]);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
key: new TextEncoder().encode(secret),
|
|
53
|
+
alsoAccept: [new TextEncoder().encode(previous)],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
17
56
|
export class ConfigError extends Error {
|
|
18
57
|
problems;
|
|
19
58
|
constructor(problems) {
|
|
@@ -42,6 +81,23 @@ class Reader {
|
|
|
42
81
|
const value = this.env[key]?.trim();
|
|
43
82
|
return value === undefined || value === '' ? undefined : value;
|
|
44
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* An optional secret with a floor on its length.
|
|
86
|
+
*
|
|
87
|
+
* Unset is fine and means the feature is off. Set to something short is not:
|
|
88
|
+
* a short token reads as protection and is a moment's guessing, which is
|
|
89
|
+
* worse than no token because it stops anyone looking again.
|
|
90
|
+
*/
|
|
91
|
+
secret(key, minLength) {
|
|
92
|
+
const value = this.optional(key);
|
|
93
|
+
if (value === undefined)
|
|
94
|
+
return undefined;
|
|
95
|
+
if (value.length < minLength) {
|
|
96
|
+
this.problems.push(`${key} must be at least ${minLength} characters, or unset`);
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
45
101
|
/** A default is allowed here: a tunable has an obviously right value. */
|
|
46
102
|
number(key, fallback, { min = 1, max = Number.MAX_SAFE_INTEGER } = {}) {
|
|
47
103
|
const raw = this.env[key]?.trim();
|
|
@@ -90,6 +146,7 @@ export function loadConfig(env = process.env) {
|
|
|
90
146
|
const config = {
|
|
91
147
|
env: r.oneOf('NACRE_ENV', ['development', 'production'], 'development'),
|
|
92
148
|
canonicalUrl: r.url('NACRE_CANONICAL_URL'),
|
|
149
|
+
oauthAuthorizationServer: r.url('NACRE_OAUTH_AUTHORIZATION_SERVER', { required: false }),
|
|
93
150
|
logLevel: r.oneOf('NACRE_LOG_LEVEL', ['debug', 'info', 'warn', 'error'], 'info'),
|
|
94
151
|
logFormat: r.oneOf('NACRE_LOG_FORMAT', ['json', 'text'], 'json'),
|
|
95
152
|
pgUrl: r.required('NACRE_PG_URL'),
|
|
@@ -106,12 +163,21 @@ export function loadConfig(env = process.env) {
|
|
|
106
163
|
// False, and it was true. `minimal` has no reranker by definition — the
|
|
107
164
|
// profile exists to run on a laptop without a GPU — so a default of true
|
|
108
165
|
// meant the documented starting profile refused to boot until the operator
|
|
109
|
-
// turned off a feature they had not asked for.
|
|
110
|
-
// on the search path yet, so the refusal bought nothing at all.
|
|
166
|
+
// turned off a feature they had not asked for.
|
|
111
167
|
rerankerEnabled: r.boolean('NACRE_RERANKER_ENABLED', false),
|
|
168
|
+
// The candidate set a cross-encoder reorders. 50 is what
|
|
169
|
+
// docs/architecture.md specifies. The maximum is bounded because every
|
|
170
|
+
// candidate is a row hydrated from Postgres and a text sent to the model:
|
|
171
|
+
// this is the one tunable here that trades latency for quality directly,
|
|
172
|
+
// and there is a value at which a search stops answering in time.
|
|
173
|
+
rerankCandidates: r.number('NACRE_RERANK_CANDIDATES', 50, { min: 1, max: 500 }),
|
|
112
174
|
jwtIssuer: r.required('NACRE_JWT_ISSUER'),
|
|
113
175
|
jwtAudience: r.required('NACRE_JWT_AUDIENCE'),
|
|
114
176
|
accessTokenTtl: r.number('NACRE_ACCESS_TOKEN_TTL', 900, { min: 60, max: 86_400 }),
|
|
177
|
+
// 30 days. The upper bound is a year rather than unbounded: a refresh token
|
|
178
|
+
// is the credential a stolen laptop still holds, and "never expires" is a
|
|
179
|
+
// choice nobody makes deliberately.
|
|
180
|
+
refreshTokenTtl: r.number('NACRE_REFRESH_TOKEN_TTL', 2_592_000, { min: 300, max: 31_536_000 }),
|
|
115
181
|
aclCacheTtl: r.number('NACRE_ACL_CACHE_TTL', 60, { min: 0, max: 3600 }),
|
|
116
182
|
aclPropagationSla: r.number('NACRE_ACL_PROPAGATION_SLA', 60, { min: 1, max: 3600 }),
|
|
117
183
|
gcGrace: r.number('NACRE_GC_GRACE', 3600, { min: 0, max: 2_592_000 }),
|
|
@@ -124,8 +190,49 @@ export function loadConfig(env = process.env) {
|
|
|
124
190
|
aclTagHashBytes: r.number('NACRE_ACL_TAG_HASH_BYTES', 8, { min: 4, max: 32 }),
|
|
125
191
|
rateSearchPerMin: r.number('NACRE_RATE_SEARCH_PER_MIN', 60, { min: 1 }),
|
|
126
192
|
rateIngestPerHour: r.number('NACRE_RATE_INGEST_PER_HOUR', 600, { min: 1 }),
|
|
193
|
+
// Ten attempts per quarter hour, counted per email address. Low enough that
|
|
194
|
+
// guessing is not a strategy, high enough that someone who genuinely cannot
|
|
195
|
+
// remember which password they used is not locked out for the afternoon.
|
|
196
|
+
rateLoginPer15Min: r.number('NACRE_RATE_LOGIN_PER_15MIN', 10, { min: 1, max: 1000 }),
|
|
197
|
+
// The same window, counted per client instead of per address, because the
|
|
198
|
+
// per-address limit does not bound the attack people actually run: one
|
|
199
|
+
// password against ten thousand addresses never repeats a key. Six times
|
|
200
|
+
// looser, because a whole office behind one NAT is one source here and the
|
|
201
|
+
// job of this limit is to stop a directory being ground down, not to make
|
|
202
|
+
// shared egress unusable.
|
|
203
|
+
rateLoginSourcePer15Min: r.number('NACRE_RATE_LOGIN_SOURCE_PER_15MIN', 60, {
|
|
204
|
+
min: 1,
|
|
205
|
+
max: 10_000,
|
|
206
|
+
}),
|
|
207
|
+
// How many proxies sit in front of this process. Zero — the default — means
|
|
208
|
+
// X-Forwarded-For is ignored entirely and the socket address is the client.
|
|
209
|
+
//
|
|
210
|
+
// Neither default is safe, which is why this is configuration rather than a
|
|
211
|
+
// guess. Trusting the header unconditionally keys the limit above on a
|
|
212
|
+
// string the attacker picks, which is worse than having no limit: a fresh
|
|
213
|
+
// value per request costs a Redis round trip and accomplishes nothing.
|
|
214
|
+
// Ignoring it unconditionally means that behind an ingress every request
|
|
215
|
+
// carries the proxy's address, so one bad client rate-limits everybody.
|
|
216
|
+
trustProxy: r.number('NACRE_TRUST_PROXY', 0, { min: 0, max: 8 }),
|
|
217
|
+
// Optional, and off by default. Requiring a token would break every
|
|
218
|
+
// existing scrape config for a product people self-host, and the default is
|
|
219
|
+
// right for the deployment this is designed around — the port is on an
|
|
220
|
+
// internal network. It stops being right the moment somebody puts the API
|
|
221
|
+
// behind a public ingress without carving /metrics out, so the operator who
|
|
222
|
+
// knows they are in that situation has a way to say so.
|
|
223
|
+
//
|
|
224
|
+
// A minimum length, because a two-character scrape token is worse than none
|
|
225
|
+
// — it reads as protection and is a moment's guessing.
|
|
226
|
+
metricsToken: r.secret('NACRE_METRICS_TOKEN', 16),
|
|
127
227
|
maxDocumentBytes: r.number('NACRE_MAX_DOCUMENT_BYTES', 52_428_800, { min: 1024 }),
|
|
128
|
-
|
|
228
|
+
// The floor is 30 and it is not a tunable. Retention is now enforced —
|
|
229
|
+
// `prune_audit_events` deletes past this horizon — and the database refuses
|
|
230
|
+
// anything shorter, because below a month "retention" stops meaning
|
|
231
|
+
// retention and becomes a way to make recent events go away, which is the
|
|
232
|
+
// thing the append-only grant exists to prevent. Refused here rather than
|
|
233
|
+
// raised hourly by the worker: a value the deployment can never act on
|
|
234
|
+
// should stop the deployment, not fill a log.
|
|
235
|
+
auditRetentionDays: r.number('NACRE_AUDIT_RETENTION_DAYS', 400, { min: 30 }),
|
|
129
236
|
auditQueryText: r.boolean('NACRE_AUDIT_QUERY_TEXT', false),
|
|
130
237
|
};
|
|
131
238
|
// Cross-field checks. Each one is a combination that parses fine and is
|
|
@@ -135,6 +242,33 @@ export function loadConfig(env = process.env) {
|
|
|
135
242
|
'Turning reranking on is worth more than any chunking tuning, and it ' +
|
|
136
243
|
'needs somewhere to send the request — the full profile provides one.');
|
|
137
244
|
}
|
|
245
|
+
// ─── settings that would silently do nothing ───
|
|
246
|
+
//
|
|
247
|
+
// Eight variables were validated here and read nowhere. Most of those are
|
|
248
|
+
// quality-of-life and are documented as unimplemented; these two are not,
|
|
249
|
+
// because ignoring them changes something an operator is relying on.
|
|
250
|
+
//
|
|
251
|
+
// Refusing at startup rather than warning: an operator who sets these has
|
|
252
|
+
// made a decision about isolation or about collision probability, and a
|
|
253
|
+
// process that starts anyway has silently overruled them. `docs/config.md`
|
|
254
|
+
// already says a silent default is how a deployment talks to nothing and
|
|
255
|
+
// reports success — this is the same failure with a value supplied.
|
|
256
|
+
if (config.vectorTenancy !== 'collection') {
|
|
257
|
+
r.problems.push('NACRE_VECTOR_TENANCY=shared is not implemented. Every collection is named ' +
|
|
258
|
+
'per organization (org_{slug}) and there is no code path that shares one, ' +
|
|
259
|
+
'so accepting this would give you a single-collection deployment that ' +
|
|
260
|
+
'believes it is isolated. Use `collection`, which is the default.');
|
|
261
|
+
}
|
|
262
|
+
if (config.aclTagHashBytes !== 8) {
|
|
263
|
+
r.problems.push('NACRE_ACL_TAG_HASH_BYTES is not implemented: the tag width is fixed at 8 ' +
|
|
264
|
+
'bytes in the code that writes and matches tags. Setting it changes the ' +
|
|
265
|
+
'collision probability you think you have and nothing else. Leave it at 8.');
|
|
266
|
+
}
|
|
267
|
+
if (config.refreshTokenTtl <= config.accessTokenTtl) {
|
|
268
|
+
r.problems.push('NACRE_REFRESH_TOKEN_TTL is not longer than NACRE_ACCESS_TOKEN_TTL. A refresh ' +
|
|
269
|
+
'token that expires no later than the access token it renews cannot renew ' +
|
|
270
|
+
'anything, so every session would end at the first refresh.');
|
|
271
|
+
}
|
|
138
272
|
if (config.aclCacheTtl > config.aclPropagationSla) {
|
|
139
273
|
// The cache would still be serving a revoked grant after the SLA has
|
|
140
274
|
// passed, and nacre_acl_propagation_lag_seconds would report compliance.
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../config.ts"],"names":[],"mappings":"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"}
|
package/dist/db/client.d.ts
CHANGED
|
@@ -34,5 +34,44 @@ export interface WithOrgOptions {
|
|
|
34
34
|
*/
|
|
35
35
|
readonly role?: string;
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Run a lookup that resolves a credential, which cannot be scoped to an
|
|
39
|
+
* organization because the credential is what says which organization it is.
|
|
40
|
+
*
|
|
41
|
+
* `app.authenticating` opens a second row-level security policy on
|
|
42
|
+
* `service_accounts` for the length of the transaction — see migration 0008.
|
|
43
|
+
* Without it the query raises `unrecognized configuration parameter` on any
|
|
44
|
+
* connection that is subject to row-level security, which is every connection
|
|
45
|
+
* a deployment following `docs/config.md` will make.
|
|
46
|
+
*
|
|
47
|
+
* **This is the one path in the system that reads across tenants, and it must
|
|
48
|
+
* stay the only one.** Two things keep it narrow, and both are load-bearing:
|
|
49
|
+
* the flag is `SET LOCAL`, so it cannot outlive the transaction or come back
|
|
50
|
+
* on a pooled connection, and the policy it opens is `FOR SELECT`, so this
|
|
51
|
+
* cannot write a row into an organization it has not identified yet.
|
|
52
|
+
*
|
|
53
|
+
* Read the credential and nothing else. Anything joined from tenant data here
|
|
54
|
+
* is a cross-tenant read with the guard rail switched off.
|
|
55
|
+
*/
|
|
56
|
+
export declare function whileAuthenticating<T>(pool: Pool, fn: (client: PoolClient) => Promise<T>, options?: WithOrgOptions): Promise<T>;
|
|
57
|
+
/**
|
|
58
|
+
* Run a query that legitimately spans every tenant, under the role the schema
|
|
59
|
+
* gives BYPASSRLS.
|
|
60
|
+
*
|
|
61
|
+
* The worker's queue is the only thing that needs this: claiming the next
|
|
62
|
+
* document to index means looking at all of them, which is what a queue is.
|
|
63
|
+
* `0001_init.sql` has said "background worker jobs run under a separate role
|
|
64
|
+
* with BYPASSRLS" since the beginning and no migration created that role until
|
|
65
|
+
* 0008, so these queries ran with no organization set and raised
|
|
66
|
+
* `unrecognized configuration parameter` on any connection subject to
|
|
67
|
+
* row-level security — every connection a deployment following
|
|
68
|
+
* `docs/config.md` makes. Indexing stopped entirely, and the Compose stack
|
|
69
|
+
* never showed it because `POSTGRES_USER` connects as a superuser.
|
|
70
|
+
*
|
|
71
|
+
* **This turns the second line of defence off.** Every query run inside it
|
|
72
|
+
* names `org_id` explicitly, because with row-level security bypassed that is
|
|
73
|
+
* the only line left.
|
|
74
|
+
*/
|
|
75
|
+
export declare function acrossOrganizations<T>(pool: Pool, fn: (client: PoolClient) => Promise<T>, role?: string): Promise<T>;
|
|
37
76
|
export declare function withOrg<T>(pool: Pool, orgId: string, fn: (client: PoolClient) => Promise<T>, options?: WithOrgOptions): Promise<T>;
|
|
38
77
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/db/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../db/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,KAAK,UAAU,EAAE,MAAM,IAAI,CAAA;AAE1C,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI,CAEnD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;;;OASG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,wBAAsB,OAAO,CAAC,CAAC,EAC7B,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,EACtC,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,CAAC,CAAC,CA0BZ"}
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../db/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,KAAK,UAAU,EAAE,MAAM,IAAI,CAAA;AAE1C,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI,CAEnD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;;;OASG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EACzC,IAAI,EAAE,IAAI,EACV,EAAE,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,EACtC,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,CAAC,CAAC,CAsBZ;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EACzC,IAAI,EAAE,IAAI,EACV,EAAE,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,EACtC,IAAI,SAAiB,GACpB,OAAO,CAAC,CAAC,CAAC,CAiBZ;AAED,wBAAsB,OAAO,CAAC,CAAC,EAC7B,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,EACtC,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,CAAC,CAAC,CA0BZ"}
|
package/dist/db/client.js
CHANGED
|
@@ -2,6 +2,88 @@ import { Pool } from 'pg';
|
|
|
2
2
|
export function createPool(options) {
|
|
3
3
|
return new Pool({ connectionString: options.connectionString, max: options.max ?? 20 });
|
|
4
4
|
}
|
|
5
|
+
/**
|
|
6
|
+
* Run a lookup that resolves a credential, which cannot be scoped to an
|
|
7
|
+
* organization because the credential is what says which organization it is.
|
|
8
|
+
*
|
|
9
|
+
* `app.authenticating` opens a second row-level security policy on
|
|
10
|
+
* `service_accounts` for the length of the transaction — see migration 0008.
|
|
11
|
+
* Without it the query raises `unrecognized configuration parameter` on any
|
|
12
|
+
* connection that is subject to row-level security, which is every connection
|
|
13
|
+
* a deployment following `docs/config.md` will make.
|
|
14
|
+
*
|
|
15
|
+
* **This is the one path in the system that reads across tenants, and it must
|
|
16
|
+
* stay the only one.** Two things keep it narrow, and both are load-bearing:
|
|
17
|
+
* the flag is `SET LOCAL`, so it cannot outlive the transaction or come back
|
|
18
|
+
* on a pooled connection, and the policy it opens is `FOR SELECT`, so this
|
|
19
|
+
* cannot write a row into an organization it has not identified yet.
|
|
20
|
+
*
|
|
21
|
+
* Read the credential and nothing else. Anything joined from tenant data here
|
|
22
|
+
* is a cross-tenant read with the guard rail switched off.
|
|
23
|
+
*/
|
|
24
|
+
export async function whileAuthenticating(pool, fn, options = {}) {
|
|
25
|
+
const client = await pool.connect();
|
|
26
|
+
try {
|
|
27
|
+
await client.query('BEGIN');
|
|
28
|
+
if (options.role !== undefined) {
|
|
29
|
+
if (!/^[a-z_][a-z0-9_]*$/i.test(options.role)) {
|
|
30
|
+
throw new Error(`refusing to set an unrecognizable role name: ${options.role}`);
|
|
31
|
+
}
|
|
32
|
+
await client.query(`SET LOCAL ROLE ${options.role}`);
|
|
33
|
+
}
|
|
34
|
+
// `true` is the local flag: it goes away with the transaction, so a pooled
|
|
35
|
+
// connection cannot hand it to the next caller.
|
|
36
|
+
await client.query('SELECT set_config($1, $2, true)', ['app.authenticating', 'on']);
|
|
37
|
+
const result = await fn(client);
|
|
38
|
+
await client.query('COMMIT');
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
catch (cause) {
|
|
42
|
+
await client.query('ROLLBACK').catch(() => { });
|
|
43
|
+
throw cause;
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
client.release();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Run a query that legitimately spans every tenant, under the role the schema
|
|
51
|
+
* gives BYPASSRLS.
|
|
52
|
+
*
|
|
53
|
+
* The worker's queue is the only thing that needs this: claiming the next
|
|
54
|
+
* document to index means looking at all of them, which is what a queue is.
|
|
55
|
+
* `0001_init.sql` has said "background worker jobs run under a separate role
|
|
56
|
+
* with BYPASSRLS" since the beginning and no migration created that role until
|
|
57
|
+
* 0008, so these queries ran with no organization set and raised
|
|
58
|
+
* `unrecognized configuration parameter` on any connection subject to
|
|
59
|
+
* row-level security — every connection a deployment following
|
|
60
|
+
* `docs/config.md` makes. Indexing stopped entirely, and the Compose stack
|
|
61
|
+
* never showed it because `POSTGRES_USER` connects as a superuser.
|
|
62
|
+
*
|
|
63
|
+
* **This turns the second line of defence off.** Every query run inside it
|
|
64
|
+
* names `org_id` explicitly, because with row-level security bypassed that is
|
|
65
|
+
* the only line left.
|
|
66
|
+
*/
|
|
67
|
+
export async function acrossOrganizations(pool, fn, role = 'nacre_worker') {
|
|
68
|
+
const client = await pool.connect();
|
|
69
|
+
try {
|
|
70
|
+
await client.query('BEGIN');
|
|
71
|
+
if (!/^[a-z_][a-z0-9_]*$/i.test(role)) {
|
|
72
|
+
throw new Error(`refusing to set an unrecognizable role name: ${role}`);
|
|
73
|
+
}
|
|
74
|
+
await client.query(`SET LOCAL ROLE ${role}`);
|
|
75
|
+
const result = await fn(client);
|
|
76
|
+
await client.query('COMMIT');
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
catch (cause) {
|
|
80
|
+
await client.query('ROLLBACK').catch(() => { });
|
|
81
|
+
throw cause;
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
client.release();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
5
87
|
export async function withOrg(pool, orgId, fn, options = {}) {
|
|
6
88
|
const client = await pool.connect();
|
|
7
89
|
try {
|
package/dist/db/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../db/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAmB,MAAM,IAAI,CAAA;AAO1C,MAAM,UAAU,UAAU,CAAC,OAAkB;IAC3C,OAAO,IAAI,IAAI,CAAC,EAAE,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC,CAAA;AACzF,CAAC;AAiCD,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,IAAU,EACV,KAAa,EACb,EAAsC,EACtC,UAA0B,EAAE;IAE5B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAA;IACnC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAC3B,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC/B,uEAAuE;YACvE,+DAA+D;YAC/D,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC9C,MAAM,IAAI,KAAK,CAAC,gDAAgD,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;YACjF,CAAC;YACD,MAAM,MAAM,CAAC,KAAK,CAAC,kBAAkB,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;QACtD,CAAC;QACD,MAAM,MAAM,CAAC,KAAK,CAAC,iCAAiC,EAAE,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC,CAAA;QACjF,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,MAAM,CAAC,CAAA;QAC/B,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;QAC5B,OAAO,MAAM,CAAA;IACf,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;YACxC,2EAA2E;YAC3E,0EAA0E;YAC1E,iEAAiE;QACnE,CAAC,CAAC,CAAA;QACF,MAAM,KAAK,CAAA;IACb,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,OAAO,EAAE,CAAA;IAClB,CAAC;AACH,CAAC"}
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../db/client.ts"],"names":[],"mappings":"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"}
|
package/dist/db/migrate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../db/migrate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../db/migrate.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAuB,GAAG,SAAS,SAAS,EAAE,CAyBjF;AAeD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CACpC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,OAAO,CAC3B,gBAAgB,EAAE,MAAM,EACxB,UAAU,GAAE,SAAS,SAAS,EAAqB,GAClD,OAAO,CAAC,aAAa,CAAC,CA4FxB"}
|
package/dist/db/migrate.js
CHANGED
|
@@ -3,6 +3,19 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { Client } from 'pg';
|
|
5
5
|
const MIGRATIONS_DIR = fileURLToPath(new URL('../migrations/', import.meta.url));
|
|
6
|
+
/**
|
|
7
|
+
* How long a migration may wait for a lock before giving up. See the call site.
|
|
8
|
+
*
|
|
9
|
+
* A migration that needs longer can say so: `SET LOCAL lock_timeout` inside the
|
|
10
|
+
* file overrides this for the rest of that transaction, and it is the right
|
|
11
|
+
* place for the exception because the migration is what knows it is unusual.
|
|
12
|
+
*
|
|
13
|
+
* `statement_timeout` is deliberately not touched. Some managed platforms set
|
|
14
|
+
* one globally and a long index build will hit it — but that is the operator's
|
|
15
|
+
* configuration, the failure rolls back cleanly, and quietly overriding a bound
|
|
16
|
+
* somebody chose is worse than failing where they can see it.
|
|
17
|
+
*/
|
|
18
|
+
const LOCK_TIMEOUT = '10s';
|
|
6
19
|
/**
|
|
7
20
|
* Migrations in filename order. Numbered prefixes are compared as numbers, so
|
|
8
21
|
* `0010` sorts after `0009` rather than between `0001` and `0002`.
|
|
@@ -81,6 +94,30 @@ export async function migrate(connectionString, migrations = loadMigrations()) {
|
|
|
81
94
|
}
|
|
82
95
|
await client.query('BEGIN');
|
|
83
96
|
try {
|
|
97
|
+
// Bounded waiting, set inside the transaction so it reverts on its own.
|
|
98
|
+
//
|
|
99
|
+
// `ALTER TABLE` takes ACCESS EXCLUSIVE, which conflicts with everything
|
|
100
|
+
// including a plain SELECT. With no timeout the sequence on a busy
|
|
101
|
+
// database is: the ALTER waits behind one long-running query, and every
|
|
102
|
+
// statement arriving afterwards queues behind the ALTER — because a
|
|
103
|
+
// lock request is not overtaken by weaker ones. One slow reporting
|
|
104
|
+
// query therefore stops the entire application for as long as it runs,
|
|
105
|
+
// and the migration that appeared to be "just adding a column" is what
|
|
106
|
+
// the outage gets blamed on.
|
|
107
|
+
//
|
|
108
|
+
// `lock_timeout`, not `statement_timeout`: the two failures are not
|
|
109
|
+
// alike. Waiting for a lock is a scheduling problem and retrying in a
|
|
110
|
+
// quieter minute fixes it, so failing fast is free. Doing the work is
|
|
111
|
+
// not — a backfill or an index build on a large table legitimately
|
|
112
|
+
// takes longer than any bound worth setting, and killing it halfway
|
|
113
|
+
// achieves nothing except a rollback. So the wait is capped and the
|
|
114
|
+
// work is not.
|
|
115
|
+
//
|
|
116
|
+
// Ten seconds because it is far longer than any lock this schema takes
|
|
117
|
+
// when the queue is empty, and far shorter than a deploy timeout — the
|
|
118
|
+
// migration fails with `55P03 lock_not_available`, which names the
|
|
119
|
+
// problem, instead of hanging until someone kills it and has to guess.
|
|
120
|
+
await client.query(`SET LOCAL lock_timeout = '${LOCK_TIMEOUT}'`);
|
|
84
121
|
await client.query(migration.sql);
|
|
85
122
|
await client.query('INSERT INTO schema_migrations (name, checksum) VALUES ($1, $2)', [
|
|
86
123
|
migration.name,
|
|
@@ -91,6 +128,18 @@ export async function migrate(connectionString, migrations = loadMigrations()) {
|
|
|
91
128
|
}
|
|
92
129
|
catch (cause) {
|
|
93
130
|
await client.query('ROLLBACK');
|
|
131
|
+
// 55P03 is `lock_not_available`, and it is worth naming: it is the one
|
|
132
|
+
// failure here that says nothing about the migration. The SQL is fine,
|
|
133
|
+
// something else was holding the table, and the fix is to look at
|
|
134
|
+
// pg_stat_activity rather than at the file.
|
|
135
|
+
if (cause.code === '55P03') {
|
|
136
|
+
throw new Error(`migration ${migration.name} gave up waiting for a lock after ${LOCK_TIMEOUT} ` +
|
|
137
|
+
'and was rolled back. Something else is holding the table — a long query, an ' +
|
|
138
|
+
'idle-in-transaction session, another migration — so this is not a problem with ' +
|
|
139
|
+
'the migration itself and re-running it once the holder is gone will work. ' +
|
|
140
|
+
'The wait is capped deliberately: an ALTER queued behind one slow query blocks ' +
|
|
141
|
+
'every statement that arrives after it, which takes the application down.', { cause });
|
|
142
|
+
}
|
|
94
143
|
throw new Error(`migration ${migration.name} failed and was rolled back`, { cause });
|
|
95
144
|
}
|
|
96
145
|
}
|
package/dist/db/migrate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../db/migrate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAA;AAE3B,MAAM,cAAc,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,gBAAgB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../db/migrate.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
2
|
export * from './authz/index.js';
|
|
3
|
-
export {
|
|
3
|
+
export { parseFilters, parseMetadata, MetadataError, MAX_METADATA_KEYS, MAX_METADATA_KEY_LENGTH, MAX_METADATA_LIST, MAX_METADATA_VALUE_LENGTH, } from './metadata.js';
|
|
4
|
+
export type { Metadata } from './metadata.js';
|
|
5
|
+
export { loadConfig, loadJwtKeys, ConfigError } from './config.js';
|
|
6
|
+
export { protectedResourceMetadata, PROTECTED_RESOURCE_PATH } from './oauth.js';
|
|
7
|
+
export type { ProtectedResourceMetadata } from './oauth.js';
|
|
8
|
+
export type { JwtKeys } from './config.js';
|
|
4
9
|
export type { Config } from './config.js';
|
|
5
|
-
export { createPool, withOrg } from './db/client.js';
|
|
10
|
+
export { acrossOrganizations, createPool, whileAuthenticating, withOrg } from './db/client.js';
|
|
6
11
|
export type { DbOptions, WithOrgOptions } from './db/client.js';
|
|
7
12
|
export { migrate, loadMigrations } from './db/migrate.js';
|
|
8
13
|
export { loadGrants, loadGroupsVersion, loadScopeTree, PostgresGroupGraph } from './authz/store.js';
|
|
9
|
-
export { VectorStore } from './vector/search.js';
|
|
14
|
+
export { explainQdrant, VectorStore } from './vector/search.js';
|
|
10
15
|
export type { Hit, SearchRequest, VectorStoreOptions } from './vector/search.js';
|
|
11
16
|
export { buildHybridQuery, collectionConfig, collectionName, PAYLOAD_INDEXES, vectorName } from './vector/query.js';
|
|
12
17
|
export type { Branch, HybridQuery } from './vector/query.js';
|
|
13
18
|
export { Counter, Gauge, Histogram, Registry, createMetrics } from './metrics.js';
|
|
14
19
|
export type { Labels, Metrics } from './metrics.js';
|
|
15
20
|
export { collectDatabaseGauges } from './observability.js';
|
|
21
|
+
export { Redis, RedisError } from './redis.js';
|
|
22
|
+
export type { RedisOptions } from './redis.js';
|
|
23
|
+
export { reindexProgress, toStateJson, fromStateJson } from './reindex.js';
|
|
24
|
+
export type { ReindexState } from './reindex.js';
|
|
25
|
+
export { hashingLoad, hashPassword, needsRehash, spendVerificationTime, TooBusy, verifyPassword, } from './passwords.js';
|
|
26
|
+
export { installGuards, onListenError } from './lifecycle.js';
|
|
27
|
+
export type { Guards } from './lifecycle.js';
|
|
16
28
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EACL,YAAY,EACZ,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,uBAAuB,EACvB,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAClE,OAAO,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;AAC/E,YAAY,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAA;AAC3D,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAC1C,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAC9F,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/D,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACnG,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAC/D,YAAY,EAAE,GAAG,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAChF,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnH,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC5D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AACjF,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC9C,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC1E,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,EACL,WAAW,EACX,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,OAAO,EACP,cAAc,GACf,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC7D,YAAY,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
2
|
export * from './authz/index.js';
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
3
|
+
export { parseFilters, parseMetadata, MetadataError, MAX_METADATA_KEYS, MAX_METADATA_KEY_LENGTH, MAX_METADATA_LIST, MAX_METADATA_VALUE_LENGTH, } from './metadata.js';
|
|
4
|
+
export { loadConfig, loadJwtKeys, ConfigError } from './config.js';
|
|
5
|
+
export { protectedResourceMetadata, PROTECTED_RESOURCE_PATH } from './oauth.js';
|
|
6
|
+
export { acrossOrganizations, createPool, whileAuthenticating, withOrg } from './db/client.js';
|
|
5
7
|
export { migrate, loadMigrations } from './db/migrate.js';
|
|
6
8
|
export { loadGrants, loadGroupsVersion, loadScopeTree, PostgresGroupGraph } from './authz/store.js';
|
|
7
|
-
export { VectorStore } from './vector/search.js';
|
|
9
|
+
export { explainQdrant, VectorStore } from './vector/search.js';
|
|
8
10
|
export { buildHybridQuery, collectionConfig, collectionName, PAYLOAD_INDEXES, vectorName } from './vector/query.js';
|
|
9
11
|
export { Counter, Gauge, Histogram, Registry, createMetrics } from './metrics.js';
|
|
10
12
|
export { collectDatabaseGauges } from './observability.js';
|
|
13
|
+
export { Redis, RedisError } from './redis.js';
|
|
14
|
+
export { reindexProgress, toStateJson, fromStateJson } from './reindex.js';
|
|
15
|
+
export { hashingLoad, hashPassword, needsRehash, spendVerificationTime, TooBusy, verifyPassword, } from './passwords.js';
|
|
16
|
+
export { installGuards, onListenError } from './lifecycle.js';
|
|
11
17
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EACL,YAAY,EACZ,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,uBAAuB,EACvB,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,eAAe,CAAA;AAEtB,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAClE,OAAO,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;AAI/E,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAE9F,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACnG,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAE/D,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAEnH,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAEjF,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE9C,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAE1E,OAAO,EACL,WAAW,EACX,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,OAAO,EACP,cAAc,GACf,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What every process does when it is asked to stop, and when it is about to
|
|
3
|
+
* die for a reason nobody wrote down.
|
|
4
|
+
*
|
|
5
|
+
* Three processes ship here — api, mcp, worker — and none of them had any of
|
|
6
|
+
* this. The consequences were all of the same shape: a failure that produced no
|
|
7
|
+
* log line, so the operator saw a container restart and had nothing to read.
|
|
8
|
+
*
|
|
9
|
+
* - **No `unhandledRejection` handler.** Node terminates the process on one,
|
|
10
|
+
* with a raw stack on stderr and no service name, no timestamp in the format
|
|
11
|
+
* every other line uses, and nothing to join to a request.
|
|
12
|
+
* - **No `error` listener on `listen`.** `EADDRINUSE` is emitted on the server
|
|
13
|
+
* *after* `main()` has already resolved, so `main().catch` never sees it and
|
|
14
|
+
* the careful ConfigError path — which exits 2 with an explanation — is
|
|
15
|
+
* bypassed by the one startup failure an operator hits most.
|
|
16
|
+
* - **No deadline on shutdown.** `server.close()` waits for in-flight requests
|
|
17
|
+
* with no upper bound. One request blocked on a slow dependency means the
|
|
18
|
+
* callback never runs, the pool is never released, and the orchestrator
|
|
19
|
+
* SIGKILLs at the end of its grace period — turning every rolling deploy into
|
|
20
|
+
* an abrupt one, silently.
|
|
21
|
+
*/
|
|
22
|
+
export interface Guards {
|
|
23
|
+
/** Named in every line, so two processes in one log stream are tellable apart. */
|
|
24
|
+
readonly service: string;
|
|
25
|
+
/**
|
|
26
|
+
* Release what the process holds. Called once, with a deadline.
|
|
27
|
+
*
|
|
28
|
+
* Whatever it does, it must not be the only thing standing between the
|
|
29
|
+
* process and exiting: if it hangs, the deadline below exits anyway.
|
|
30
|
+
*/
|
|
31
|
+
readonly shutdown?: () => Promise<void> | void;
|
|
32
|
+
/** How long `shutdown` gets. Shorter than any orchestrator's grace period. */
|
|
33
|
+
readonly graceMs?: number;
|
|
34
|
+
/** Injected in tests. Nothing else should be replacing this. */
|
|
35
|
+
readonly exit?: (code: number) => void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Install the handlers. Returns a `stop` to call from a signal handler.
|
|
39
|
+
*
|
|
40
|
+
* Idempotent: a second SIGTERM while shutting down is ignored rather than
|
|
41
|
+
* starting a second shutdown, because an orchestrator that sends one and then
|
|
42
|
+
* loses patience sends another.
|
|
43
|
+
*/
|
|
44
|
+
export declare function installGuards(guards: Guards): {
|
|
45
|
+
stop: (signal: string) => void;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Fail a `listen` the way every other startup failure fails.
|
|
49
|
+
*
|
|
50
|
+
* `server.listen(port, cb)` reports a bound port through the callback and a
|
|
51
|
+
* refusal through an `error` event — which arrives after `main()` has resolved,
|
|
52
|
+
* so nothing was catching it.
|
|
53
|
+
*/
|
|
54
|
+
export declare function onListenError(server: {
|
|
55
|
+
on(event: 'error', listener: (error: NodeJS.ErrnoException) => void): unknown;
|
|
56
|
+
}, service: string, port: number, exit?: (code: number) => void): void;
|
|
57
|
+
//# sourceMappingURL=lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,WAAW,MAAM;IACrB,kFAAkF;IAClF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC9C,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,gEAAgE;IAChE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACvC;AAMD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,CAmDhF;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE;IAAE,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,cAAc,KAAK,IAAI,GAAG,OAAO,CAAA;CAAE,EACzF,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAmC,GAC1D,IAAI,CAWN"}
|