@nacre.work/core 0.0.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/README.md +3 -0
- package/dist/authz/cache.d.ts +54 -0
- package/dist/authz/cache.d.ts.map +1 -0
- package/dist/authz/cache.js +63 -0
- package/dist/authz/cache.js.map +1 -0
- package/dist/authz/filter.d.ts +60 -0
- package/dist/authz/filter.d.ts.map +1 -0
- package/dist/authz/filter.js +54 -0
- package/dist/authz/filter.js.map +1 -0
- package/dist/authz/index.d.ts +15 -0
- package/dist/authz/index.d.ts.map +1 -0
- package/dist/authz/index.js +9 -0
- package/dist/authz/index.js.map +1 -0
- package/dist/authz/permissions.d.ts +20 -0
- package/dist/authz/permissions.d.ts.map +1 -0
- package/dist/authz/permissions.js +43 -0
- package/dist/authz/permissions.js.map +1 -0
- package/dist/authz/principals.d.ts +34 -0
- package/dist/authz/principals.d.ts.map +1 -0
- package/dist/authz/principals.js +42 -0
- package/dist/authz/principals.js.map +1 -0
- package/dist/authz/reference.d.ts +25 -0
- package/dist/authz/reference.d.ts.map +1 -0
- package/dist/authz/reference.js +34 -0
- package/dist/authz/reference.js.map +1 -0
- package/dist/authz/resolve.d.ts +45 -0
- package/dist/authz/resolve.d.ts.map +1 -0
- package/dist/authz/resolve.js +98 -0
- package/dist/authz/resolve.js.map +1 -0
- package/dist/authz/scope-tree.d.ts +52 -0
- package/dist/authz/scope-tree.d.ts.map +1 -0
- package/dist/authz/scope-tree.js +56 -0
- package/dist/authz/scope-tree.js.map +1 -0
- package/dist/authz/store.d.ts +50 -0
- package/dist/authz/store.d.ts.map +1 -0
- package/dist/authz/store.js +115 -0
- package/dist/authz/store.js.map +1 -0
- package/dist/authz/tags.d.ts +40 -0
- package/dist/authz/tags.d.ts.map +1 -0
- package/dist/authz/tags.js +48 -0
- package/dist/authz/tags.js.map +1 -0
- package/dist/config.d.ts +73 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +157 -0
- package/dist/config.js.map +1 -0
- package/dist/db/client.d.ts +38 -0
- package/dist/db/client.d.ts.map +1 -0
- package/dist/db/client.js +34 -0
- package/dist/db/client.js.map +1 -0
- package/dist/db/migrate.d.ts +32 -0
- package/dist/db/migrate.d.ts.map +1 -0
- package/dist/db/migrate.js +103 -0
- package/dist/db/migrate.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics.d.ts +77 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +179 -0
- package/dist/metrics.js.map +1 -0
- package/dist/migrate-main.d.ts +2 -0
- package/dist/migrate-main.d.ts.map +1 -0
- package/dist/migrate-main.js +34 -0
- package/dist/migrate-main.js.map +1 -0
- package/dist/migrations/0001_init.sql +226 -0
- package/dist/migrations/0002_rls_coverage.sql +128 -0
- package/dist/migrations/0003_groups_version.sql +48 -0
- package/dist/migrations/0004_acl_version.sql +26 -0
- package/dist/migrations/0005_grants_bump_version.sql +38 -0
- package/dist/migrations/0006_layer_slug_unique.sql +47 -0
- package/dist/migrations/0007_index_lease.sql +42 -0
- package/dist/observability.d.ts +11 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +55 -0
- package/dist/observability.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types.d.ts +54 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/dist/vector/query.d.ts +140 -0
- package/dist/vector/query.d.ts.map +1 -0
- package/dist/vector/query.js +94 -0
- package/dist/vector/query.js.map +1 -0
- package/dist/vector/search.d.ts +62 -0
- package/dist/vector/search.d.ts.map +1 -0
- package/dist/vector/search.js +98 -0
- package/dist/vector/search.js.map +1 -0
- package/package.json +39 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration, validated whole at startup.
|
|
3
|
+
*
|
|
4
|
+
* Two rules from docs/config.md, and both are about the same failure:
|
|
5
|
+
*
|
|
6
|
+
* **Validate everything at boot and exit if anything is missing or
|
|
7
|
+
* contradictory.** Not on first use. A service that starts and then fails the
|
|
8
|
+
* first request that needs the missing value looks healthy to an orchestrator,
|
|
9
|
+
* gets traffic, and reports the problem as an error rate.
|
|
10
|
+
*
|
|
11
|
+
* **No silent defaults for secrets or URLs.** A default that quietly points at
|
|
12
|
+
* localhost is how a production deployment ends up talking to nothing and
|
|
13
|
+
* reporting success. Defaults are fine for tunables — a cache TTL has an
|
|
14
|
+
* obviously right value — and never fine for anything naming a host or
|
|
15
|
+
* carrying a credential.
|
|
16
|
+
*/
|
|
17
|
+
export class ConfigError extends Error {
|
|
18
|
+
problems;
|
|
19
|
+
constructor(problems) {
|
|
20
|
+
super(`configuration is not usable:\n${problems.map((p) => ` - ${p}`).join('\n')}\n` +
|
|
21
|
+
'Every one of these is required at startup. See docs/config.md.');
|
|
22
|
+
this.name = 'ConfigError';
|
|
23
|
+
this.problems = problems;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
class Reader {
|
|
27
|
+
env;
|
|
28
|
+
problems = [];
|
|
29
|
+
constructor(env) {
|
|
30
|
+
this.env = env;
|
|
31
|
+
}
|
|
32
|
+
/** Required, with no fallback. Used for anything naming a host or a secret. */
|
|
33
|
+
required(key) {
|
|
34
|
+
const value = this.env[key]?.trim();
|
|
35
|
+
if (value === undefined || value === '') {
|
|
36
|
+
this.problems.push(`${key} is not set`);
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
optional(key) {
|
|
42
|
+
const value = this.env[key]?.trim();
|
|
43
|
+
return value === undefined || value === '' ? undefined : value;
|
|
44
|
+
}
|
|
45
|
+
/** A default is allowed here: a tunable has an obviously right value. */
|
|
46
|
+
number(key, fallback, { min = 1, max = Number.MAX_SAFE_INTEGER } = {}) {
|
|
47
|
+
const raw = this.env[key]?.trim();
|
|
48
|
+
if (raw === undefined || raw === '')
|
|
49
|
+
return fallback;
|
|
50
|
+
const value = Number(raw);
|
|
51
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || value < min || value > max) {
|
|
52
|
+
this.problems.push(`${key} must be an integer between ${min} and ${max}, got ${JSON.stringify(raw)}`);
|
|
53
|
+
return fallback;
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
boolean(key, fallback) {
|
|
58
|
+
const raw = this.env[key]?.trim().toLowerCase();
|
|
59
|
+
if (raw === undefined || raw === '')
|
|
60
|
+
return fallback;
|
|
61
|
+
if (raw === 'true' || raw === 'false')
|
|
62
|
+
return raw === 'true';
|
|
63
|
+
this.problems.push(`${key} must be true or false, got ${JSON.stringify(raw)}`);
|
|
64
|
+
return fallback;
|
|
65
|
+
}
|
|
66
|
+
oneOf(key, allowed, fallback) {
|
|
67
|
+
const raw = this.env[key]?.trim();
|
|
68
|
+
if (raw === undefined || raw === '')
|
|
69
|
+
return fallback;
|
|
70
|
+
if (allowed.includes(raw))
|
|
71
|
+
return raw;
|
|
72
|
+
this.problems.push(`${key} must be one of ${allowed.join(', ')}, got ${JSON.stringify(raw)}`);
|
|
73
|
+
return fallback;
|
|
74
|
+
}
|
|
75
|
+
url(key, { required = true } = {}) {
|
|
76
|
+
const raw = required ? this.required(key) : (this.optional(key) ?? '');
|
|
77
|
+
if (raw === '')
|
|
78
|
+
return '';
|
|
79
|
+
try {
|
|
80
|
+
new URL(raw);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
this.problems.push(`${key} is not a URL: ${JSON.stringify(raw)}`);
|
|
84
|
+
}
|
|
85
|
+
return raw;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export function loadConfig(env = process.env) {
|
|
89
|
+
const r = new Reader(env);
|
|
90
|
+
const config = {
|
|
91
|
+
env: r.oneOf('NACRE_ENV', ['development', 'production'], 'development'),
|
|
92
|
+
canonicalUrl: r.url('NACRE_CANONICAL_URL'),
|
|
93
|
+
logLevel: r.oneOf('NACRE_LOG_LEVEL', ['debug', 'info', 'warn', 'error'], 'info'),
|
|
94
|
+
logFormat: r.oneOf('NACRE_LOG_FORMAT', ['json', 'text'], 'json'),
|
|
95
|
+
pgUrl: r.required('NACRE_PG_URL'),
|
|
96
|
+
pgPoolMax: r.number('NACRE_PG_POOL_MAX', 20, { min: 1, max: 1000 }),
|
|
97
|
+
qdrantUrl: r.url('NACRE_QDRANT_URL'),
|
|
98
|
+
qdrantApiKey: r.optional('NACRE_QDRANT_API_KEY'),
|
|
99
|
+
vectorTenancy: r.oneOf('NACRE_VECTOR_TENANCY', ['collection', 'shared'], 'collection'),
|
|
100
|
+
redisUrl: r.required('NACRE_REDIS_URL'),
|
|
101
|
+
embeddingEndpoint: r.url('NACRE_DEFAULT_EMBEDDING_ENDPOINT'),
|
|
102
|
+
embeddingModel: r.required('NACRE_DEFAULT_EMBEDDING_MODEL'),
|
|
103
|
+
embeddingDim: r.number('NACRE_DEFAULT_EMBEDDING_DIM', 1024, { min: 8, max: 16384 }),
|
|
104
|
+
parserEndpoint: r.url('NACRE_PARSER_ENDPOINT'),
|
|
105
|
+
rerankerEndpoint: r.optional('NACRE_RERANKER_ENDPOINT'),
|
|
106
|
+
// False, and it was true. `minimal` has no reranker by definition — the
|
|
107
|
+
// profile exists to run on a laptop without a GPU — so a default of true
|
|
108
|
+
// meant the documented starting profile refused to boot until the operator
|
|
109
|
+
// turned off a feature they had not asked for. It is also not implemented
|
|
110
|
+
// on the search path yet, so the refusal bought nothing at all.
|
|
111
|
+
rerankerEnabled: r.boolean('NACRE_RERANKER_ENABLED', false),
|
|
112
|
+
jwtIssuer: r.required('NACRE_JWT_ISSUER'),
|
|
113
|
+
jwtAudience: r.required('NACRE_JWT_AUDIENCE'),
|
|
114
|
+
accessTokenTtl: r.number('NACRE_ACCESS_TOKEN_TTL', 900, { min: 60, max: 86_400 }),
|
|
115
|
+
aclCacheTtl: r.number('NACRE_ACL_CACHE_TTL', 60, { min: 0, max: 3600 }),
|
|
116
|
+
aclPropagationSla: r.number('NACRE_ACL_PROPAGATION_SLA', 60, { min: 1, max: 3600 }),
|
|
117
|
+
gcGrace: r.number('NACRE_GC_GRACE', 3600, { min: 0, max: 2_592_000 }),
|
|
118
|
+
// 15 minutes: long enough for a large PDF through parse, chunk, embed, and
|
|
119
|
+
// upsert, short enough that a drained node's documents are not stuck for an
|
|
120
|
+
// afternoon. The minimum is 60 rather than 0 — a zero lease reclaims a
|
|
121
|
+
// document the instant it is claimed, which is a loop, not a setting.
|
|
122
|
+
indexLease: r.number('NACRE_INDEX_LEASE', 900, { min: 60, max: 86_400 }),
|
|
123
|
+
indexMaxAttempts: r.number('NACRE_INDEX_MAX_ATTEMPTS', 5, { min: 1, max: 100 }),
|
|
124
|
+
aclTagHashBytes: r.number('NACRE_ACL_TAG_HASH_BYTES', 8, { min: 4, max: 32 }),
|
|
125
|
+
rateSearchPerMin: r.number('NACRE_RATE_SEARCH_PER_MIN', 60, { min: 1 }),
|
|
126
|
+
rateIngestPerHour: r.number('NACRE_RATE_INGEST_PER_HOUR', 600, { min: 1 }),
|
|
127
|
+
maxDocumentBytes: r.number('NACRE_MAX_DOCUMENT_BYTES', 52_428_800, { min: 1024 }),
|
|
128
|
+
auditRetentionDays: r.number('NACRE_AUDIT_RETENTION_DAYS', 400, { min: 1 }),
|
|
129
|
+
auditQueryText: r.boolean('NACRE_AUDIT_QUERY_TEXT', false),
|
|
130
|
+
};
|
|
131
|
+
// Cross-field checks. Each one is a combination that parses fine and is
|
|
132
|
+
// wrong, which is exactly the class a per-variable check cannot catch.
|
|
133
|
+
if (config.rerankerEnabled && config.rerankerEndpoint === undefined) {
|
|
134
|
+
r.problems.push('NACRE_RERANKER_ENABLED is true but NACRE_RERANKER_ENDPOINT is not set. ' +
|
|
135
|
+
'Turning reranking on is worth more than any chunking tuning, and it ' +
|
|
136
|
+
'needs somewhere to send the request — the full profile provides one.');
|
|
137
|
+
}
|
|
138
|
+
if (config.aclCacheTtl > config.aclPropagationSla) {
|
|
139
|
+
// The cache would still be serving a revoked grant after the SLA has
|
|
140
|
+
// passed, and nacre_acl_propagation_lag_seconds would report compliance.
|
|
141
|
+
r.problems.push(`NACRE_ACL_CACHE_TTL (${config.aclCacheTtl}) is longer than ` +
|
|
142
|
+
`NACRE_ACL_PROPAGATION_SLA (${config.aclPropagationSla}); a revoked grant ` +
|
|
143
|
+
'would still be served after the SLA it promises.');
|
|
144
|
+
}
|
|
145
|
+
if (config.env === 'production' && config.canonicalUrl.startsWith('http://')) {
|
|
146
|
+
// The canonical URL is the OAuth issuer and goes into every token ever
|
|
147
|
+
// issued. Over plaintext it is also the thing an attacker rewrites.
|
|
148
|
+
r.problems.push('NACRE_CANONICAL_URL must be https in production; it is the OAuth issuer');
|
|
149
|
+
}
|
|
150
|
+
if (config.env === 'production' && /(^|\/\/)(localhost|127\.0\.0\.1)/.test(config.canonicalUrl)) {
|
|
151
|
+
r.problems.push('NACRE_CANONICAL_URL points at localhost in production');
|
|
152
|
+
}
|
|
153
|
+
if (r.problems.length > 0)
|
|
154
|
+
throw new ConfigError(r.problems);
|
|
155
|
+
return config;
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Pool, type PoolClient } from 'pg';
|
|
2
|
+
export interface DbOptions {
|
|
3
|
+
readonly connectionString: string;
|
|
4
|
+
readonly max?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function createPool(options: DbOptions): Pool;
|
|
7
|
+
/**
|
|
8
|
+
* Run `fn` on a connection scoped to one organization.
|
|
9
|
+
*
|
|
10
|
+
* `app.current_org` is what every row-level security policy reads. It is set
|
|
11
|
+
* with `set_config(..., true)` — local to the transaction — so it cannot leak
|
|
12
|
+
* to the next borrower of a pooled connection. A pool plus a session-level
|
|
13
|
+
* setting is the classic way this goes wrong: the setting outlives the request
|
|
14
|
+
* and the next tenant inherits it.
|
|
15
|
+
*
|
|
16
|
+
* The transaction is therefore not optional. It is the thing that scopes the
|
|
17
|
+
* setting.
|
|
18
|
+
*
|
|
19
|
+
* This is the second line of defense, not the mechanism. Queries still name
|
|
20
|
+
* `org_id` themselves, and invariant I1 is still re-checked when a response is
|
|
21
|
+
* serialized. RLS exists so that one forgotten `WHERE` returns nothing instead
|
|
22
|
+
* of another tenant's rows.
|
|
23
|
+
*/
|
|
24
|
+
export interface WithOrgOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Switch to this role for the transaction, with `SET LOCAL ROLE`.
|
|
27
|
+
*
|
|
28
|
+
* Row-level security does not apply to a superuser at all, and applies to the
|
|
29
|
+
* table owner only where the table is FORCE'd. Migrations run as the owner,
|
|
30
|
+
* so a deployment that reuses that connection for the application gets
|
|
31
|
+
* policies that are enabled and inert. Connecting as an unprivileged role is
|
|
32
|
+
* the fix; this option exists for deployments that cannot, and for tests that
|
|
33
|
+
* need to prove the policies bite.
|
|
34
|
+
*/
|
|
35
|
+
readonly role?: string;
|
|
36
|
+
}
|
|
37
|
+
export declare function withOrg<T>(pool: Pool, orgId: string, fn: (client: PoolClient) => Promise<T>, options?: WithOrgOptions): Promise<T>;
|
|
38
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Pool } from 'pg';
|
|
2
|
+
export function createPool(options) {
|
|
3
|
+
return new Pool({ connectionString: options.connectionString, max: options.max ?? 20 });
|
|
4
|
+
}
|
|
5
|
+
export async function withOrg(pool, orgId, fn, options = {}) {
|
|
6
|
+
const client = await pool.connect();
|
|
7
|
+
try {
|
|
8
|
+
await client.query('BEGIN');
|
|
9
|
+
if (options.role !== undefined) {
|
|
10
|
+
// Not parameterizable — a role name is an identifier, not a value. The
|
|
11
|
+
// caller supplies it from configuration, never from a request.
|
|
12
|
+
if (!/^[a-z_][a-z0-9_]*$/i.test(options.role)) {
|
|
13
|
+
throw new Error(`refusing to set an unrecognizable role name: ${options.role}`);
|
|
14
|
+
}
|
|
15
|
+
await client.query(`SET LOCAL ROLE ${options.role}`);
|
|
16
|
+
}
|
|
17
|
+
await client.query('SELECT set_config($1, $2, true)', ['app.current_org', orgId]);
|
|
18
|
+
const result = await fn(client);
|
|
19
|
+
await client.query('COMMIT');
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
catch (cause) {
|
|
23
|
+
await client.query('ROLLBACK').catch(() => {
|
|
24
|
+
// A rollback that itself fails means the connection is unusable. Releasing
|
|
25
|
+
// it with an error below discards it rather than returning it to the pool
|
|
26
|
+
// with an open transaction and someone else's org setting on it.
|
|
27
|
+
});
|
|
28
|
+
throw cause;
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
client.release();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface Migration {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly sql: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Migrations in filename order. Numbered prefixes are compared as numbers, so
|
|
7
|
+
* `0010` sorts after `0009` rather than between `0001` and `0002`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function loadMigrations(dir?: string): readonly Migration[];
|
|
10
|
+
export interface MigrateResult {
|
|
11
|
+
readonly applied: readonly string[];
|
|
12
|
+
readonly skipped: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Apply every migration that has not been applied yet.
|
|
16
|
+
*
|
|
17
|
+
* Two properties worth stating, because both are load-bearing:
|
|
18
|
+
*
|
|
19
|
+
* **Each migration runs inside its own transaction**, together with the ledger
|
|
20
|
+
* row that records it. A migration that fails halfway leaves the database as it
|
|
21
|
+
* was — the alternative is a schema that is neither the old one nor the new one
|
|
22
|
+
* and no record of which, which is the worst state to debug a permissions
|
|
23
|
+
* system in.
|
|
24
|
+
*
|
|
25
|
+
* **Applied migrations are checksummed and re-checked.** Editing a file that
|
|
26
|
+
* has already run is the mistake this catches: it works on the author's fresh
|
|
27
|
+
* database and does nothing on every environment that already applied the old
|
|
28
|
+
* text, so the two silently diverge. Migrations are forward-only; fixing one
|
|
29
|
+
* means adding another.
|
|
30
|
+
*/
|
|
31
|
+
export declare function migrate(connectionString: string, migrations?: readonly Migration[]): Promise<MigrateResult>;
|
|
32
|
+
//# sourceMappingURL=migrate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../db/migrate.ts"],"names":[],"mappings":"AAQA,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,CAmDxB"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { Client } from 'pg';
|
|
5
|
+
const MIGRATIONS_DIR = fileURLToPath(new URL('../migrations/', import.meta.url));
|
|
6
|
+
/**
|
|
7
|
+
* Migrations in filename order. Numbered prefixes are compared as numbers, so
|
|
8
|
+
* `0010` sorts after `0009` rather than between `0001` and `0002`.
|
|
9
|
+
*/
|
|
10
|
+
export function loadMigrations(dir = MIGRATIONS_DIR) {
|
|
11
|
+
let entries;
|
|
12
|
+
try {
|
|
13
|
+
entries = readdirSync(dir);
|
|
14
|
+
}
|
|
15
|
+
catch (cause) {
|
|
16
|
+
// The built package resolves this to dist/migrations, which tsc does not
|
|
17
|
+
// create — it compiles .ts and ignores .sql. Saying so beats an ENOENT
|
|
18
|
+
// naming a path the caller has no reason to recognise.
|
|
19
|
+
throw new Error(`no migrations directory at ${dir}. In a published build this means the ` +
|
|
20
|
+
'SQL was not copied into dist; see packages/core/scripts/copy-migrations.mjs.', { cause });
|
|
21
|
+
}
|
|
22
|
+
const migrations = entries.filter((f) => f.endsWith('.sql'));
|
|
23
|
+
if (migrations.length === 0) {
|
|
24
|
+
// Applying nothing and reporting success would leave a fresh database with
|
|
25
|
+
// no tables and a ledger saying everything is up to date.
|
|
26
|
+
throw new Error(`no .sql files in ${dir}; there is nothing to apply and that is not "already migrated"`);
|
|
27
|
+
}
|
|
28
|
+
return migrations
|
|
29
|
+
.sort((a, b) => Number.parseInt(a, 10) - Number.parseInt(b, 10) || a.localeCompare(b))
|
|
30
|
+
.map((name) => ({ name, sql: readFileSync(join(dir, name), 'utf8') }));
|
|
31
|
+
}
|
|
32
|
+
const LEDGER = `
|
|
33
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
34
|
+
name text PRIMARY KEY,
|
|
35
|
+
checksum text NOT NULL,
|
|
36
|
+
applied_at timestamptz NOT NULL DEFAULT now()
|
|
37
|
+
)
|
|
38
|
+
`;
|
|
39
|
+
async function checksum(sql) {
|
|
40
|
+
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(sql));
|
|
41
|
+
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Apply every migration that has not been applied yet.
|
|
45
|
+
*
|
|
46
|
+
* Two properties worth stating, because both are load-bearing:
|
|
47
|
+
*
|
|
48
|
+
* **Each migration runs inside its own transaction**, together with the ledger
|
|
49
|
+
* row that records it. A migration that fails halfway leaves the database as it
|
|
50
|
+
* was — the alternative is a schema that is neither the old one nor the new one
|
|
51
|
+
* and no record of which, which is the worst state to debug a permissions
|
|
52
|
+
* system in.
|
|
53
|
+
*
|
|
54
|
+
* **Applied migrations are checksummed and re-checked.** Editing a file that
|
|
55
|
+
* has already run is the mistake this catches: it works on the author's fresh
|
|
56
|
+
* database and does nothing on every environment that already applied the old
|
|
57
|
+
* text, so the two silently diverge. Migrations are forward-only; fixing one
|
|
58
|
+
* means adding another.
|
|
59
|
+
*/
|
|
60
|
+
export async function migrate(connectionString, migrations = loadMigrations()) {
|
|
61
|
+
const client = new Client({ connectionString });
|
|
62
|
+
await client.connect();
|
|
63
|
+
const applied = [];
|
|
64
|
+
const skipped = [];
|
|
65
|
+
try {
|
|
66
|
+
await client.query(LEDGER);
|
|
67
|
+
const { rows } = await client.query('SELECT name, checksum FROM schema_migrations');
|
|
68
|
+
const ledger = new Map(rows.map((r) => [r.name, r.checksum]));
|
|
69
|
+
for (const migration of migrations) {
|
|
70
|
+
const sum = await checksum(migration.sql);
|
|
71
|
+
const recorded = ledger.get(migration.name);
|
|
72
|
+
if (recorded !== undefined) {
|
|
73
|
+
if (recorded !== sum) {
|
|
74
|
+
throw new Error(`migration ${migration.name} was modified after it was applied ` +
|
|
75
|
+
'(checksum mismatch). Migrations are forward-only: add a new one ' +
|
|
76
|
+
'rather than editing this file, or the databases that already ran ' +
|
|
77
|
+
'the old text will never receive the change.');
|
|
78
|
+
}
|
|
79
|
+
skipped.push(migration.name);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
await client.query('BEGIN');
|
|
83
|
+
try {
|
|
84
|
+
await client.query(migration.sql);
|
|
85
|
+
await client.query('INSERT INTO schema_migrations (name, checksum) VALUES ($1, $2)', [
|
|
86
|
+
migration.name,
|
|
87
|
+
sum,
|
|
88
|
+
]);
|
|
89
|
+
await client.query('COMMIT');
|
|
90
|
+
applied.push(migration.name);
|
|
91
|
+
}
|
|
92
|
+
catch (cause) {
|
|
93
|
+
await client.query('ROLLBACK');
|
|
94
|
+
throw new Error(`migration ${migration.name} failed and was rolled back`, { cause });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
await client.end();
|
|
100
|
+
}
|
|
101
|
+
return { applied, skipped };
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=migrate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../db/migrate.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './authz/index.js';
|
|
3
|
+
export { loadConfig, ConfigError } from './config.js';
|
|
4
|
+
export type { Config } from './config.js';
|
|
5
|
+
export { createPool, withOrg } from './db/client.js';
|
|
6
|
+
export type { DbOptions, WithOrgOptions } from './db/client.js';
|
|
7
|
+
export { migrate, loadMigrations } from './db/migrate.js';
|
|
8
|
+
export { loadGrants, loadGroupsVersion, loadScopeTree, PostgresGroupGraph } from './authz/store.js';
|
|
9
|
+
export { VectorStore } from './vector/search.js';
|
|
10
|
+
export type { Hit, SearchRequest, VectorStoreOptions } from './vector/search.js';
|
|
11
|
+
export { buildHybridQuery, collectionConfig, collectionName, PAYLOAD_INDEXES, vectorName } from './vector/query.js';
|
|
12
|
+
export type { Branch, HybridQuery } from './vector/query.js';
|
|
13
|
+
export { Counter, Gauge, Histogram, Registry, createMetrics } from './metrics.js';
|
|
14
|
+
export type { Labels, Metrics } from './metrics.js';
|
|
15
|
+
export { collectDatabaseGauges } from './observability.js';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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;AACrD,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AACpD,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,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './authz/index.js';
|
|
3
|
+
export { loadConfig, ConfigError } from './config.js';
|
|
4
|
+
export { createPool, withOrg } from './db/client.js';
|
|
5
|
+
export { migrate, loadMigrations } from './db/migrate.js';
|
|
6
|
+
export { loadGrants, loadGroupsVersion, loadScopeTree, PostgresGroupGraph } from './authz/store.js';
|
|
7
|
+
export { VectorStore } from './vector/search.js';
|
|
8
|
+
export { buildHybridQuery, collectionConfig, collectionName, PAYLOAD_INDEXES, vectorName } from './vector/query.js';
|
|
9
|
+
export { Counter, Gauge, Histogram, Registry, createMetrics } from './metrics.js';
|
|
10
|
+
export { collectDatabaseGauges } from './observability.js';
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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;AAErD,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAEpD,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,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAEhD,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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A minimal Prometheus registry.
|
|
3
|
+
*
|
|
4
|
+
* Dependency-free because the exposition format is four lines of rules and this
|
|
5
|
+
* container reads documents for a living — every dependency in it is something
|
|
6
|
+
* to keep patched. If the needs here outgrow histograms and gauges, replace it
|
|
7
|
+
* with prom-client rather than growing this file.
|
|
8
|
+
*/
|
|
9
|
+
export type Labels = Readonly<Record<string, string>>;
|
|
10
|
+
interface Metric {
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly help: string;
|
|
13
|
+
readonly type: 'counter' | 'gauge' | 'histogram';
|
|
14
|
+
render(): string[];
|
|
15
|
+
}
|
|
16
|
+
export declare class Counter implements Metric {
|
|
17
|
+
#private;
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly help: string;
|
|
20
|
+
readonly type: "counter";
|
|
21
|
+
constructor(name: string, help: string);
|
|
22
|
+
inc(labels?: Labels, by?: number): void;
|
|
23
|
+
render(): string[];
|
|
24
|
+
}
|
|
25
|
+
export declare class Gauge implements Metric {
|
|
26
|
+
#private;
|
|
27
|
+
readonly name: string;
|
|
28
|
+
readonly help: string;
|
|
29
|
+
readonly type: "gauge";
|
|
30
|
+
constructor(name: string, help: string);
|
|
31
|
+
set(value: number, labels?: Labels): void;
|
|
32
|
+
reset(): void;
|
|
33
|
+
render(): string[];
|
|
34
|
+
}
|
|
35
|
+
export declare class Histogram implements Metric {
|
|
36
|
+
#private;
|
|
37
|
+
readonly name: string;
|
|
38
|
+
readonly help: string;
|
|
39
|
+
readonly buckets: readonly number[];
|
|
40
|
+
readonly type: "histogram";
|
|
41
|
+
constructor(name: string, help: string, buckets?: readonly number[]);
|
|
42
|
+
observe(seconds: number, labels?: Labels): void;
|
|
43
|
+
/** Times `fn`, recording however it ends. A failure has a duration too. */
|
|
44
|
+
time<T>(labels: Labels, fn: () => Promise<T>): Promise<T>;
|
|
45
|
+
render(): string[];
|
|
46
|
+
}
|
|
47
|
+
export declare class Registry {
|
|
48
|
+
#private;
|
|
49
|
+
register<T extends Metric>(metric: T): T;
|
|
50
|
+
/**
|
|
51
|
+
* A function run before every scrape, for values that are queries rather than
|
|
52
|
+
* counters — document counts, the propagation lag, pending tombstones.
|
|
53
|
+
*/
|
|
54
|
+
collect(fn: () => Promise<void>): void;
|
|
55
|
+
render(): Promise<string>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The metrics docs/config.md requires.
|
|
59
|
+
*
|
|
60
|
+
* `nacre_acl_propagation_lag_seconds` is the one with an alert on it, and the
|
|
61
|
+
* reason is worth restating where the metric is defined: it is the only
|
|
62
|
+
* external evidence that invariant I4 — a revoked grant reflected within the
|
|
63
|
+
* SLA — still holds. Everything else here describes how the system is doing.
|
|
64
|
+
* This one describes whether it is still correct.
|
|
65
|
+
*/
|
|
66
|
+
export declare function createMetrics(registry: Registry): {
|
|
67
|
+
searchDuration: Histogram;
|
|
68
|
+
searchResults: Counter;
|
|
69
|
+
aclDenials: Counter;
|
|
70
|
+
aclPropagationLag: Gauge;
|
|
71
|
+
ingestDuration: Histogram;
|
|
72
|
+
documents: Gauge;
|
|
73
|
+
tombstonesPending: Gauge;
|
|
74
|
+
};
|
|
75
|
+
export type Metrics = ReturnType<typeof createMetrics>;
|
|
76
|
+
export {};
|
|
77
|
+
//# sourceMappingURL=metrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;AAgBrD,UAAU,MAAM;IACd,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,WAAW,CAAA;IAChD,MAAM,IAAI,MAAM,EAAE,CAAA;CACnB;AAED,qBAAa,OAAQ,YAAW,MAAM;;IAKlC,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM;IALvB,QAAQ,CAAC,IAAI,EAAG,SAAS,CAAS;gBAIvB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM;IAKvB,GAAG,CAAC,MAAM,GAAE,MAAW,EAAE,EAAE,SAAI,GAAG,IAAI;IAOtC,MAAM,IAAI,MAAM,EAAE;CAOnB;AAED,qBAAa,KAAM,YAAW,MAAM;;IAKhC,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM;IALvB,QAAQ,CAAC,IAAI,EAAG,OAAO,CAAS;gBAIrB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM;IAKvB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,MAAW,GAAG,IAAI;IAI7C,KAAK,IAAI,IAAI;IAIb,MAAM,IAAI,MAAM,EAAE;CAInB;AAID,qBAAa,SAAU,YAAW,MAAM;;IAKpC,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE;IANrC,QAAQ,CAAC,IAAI,EAAG,WAAW,CAAS;gBAIzB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,SAAS,MAAM,EAAoB;IAKvD,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,MAAW,GAAG,IAAI;IAgBnD,2EAA2E;IACrE,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAS/D,MAAM,IAAI,MAAM,EAAE;CAmBnB;AAED,qBAAa,QAAQ;;IAInB,QAAQ,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC;IAQxC;;;OAGG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAIhC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;CAahC;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ;;;;;;;;EA8B/C;AAED,MAAM,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAA"}
|