@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.
Files changed (90) hide show
  1. package/README.md +3 -0
  2. package/dist/authz/cache.d.ts +54 -0
  3. package/dist/authz/cache.d.ts.map +1 -0
  4. package/dist/authz/cache.js +63 -0
  5. package/dist/authz/cache.js.map +1 -0
  6. package/dist/authz/filter.d.ts +60 -0
  7. package/dist/authz/filter.d.ts.map +1 -0
  8. package/dist/authz/filter.js +54 -0
  9. package/dist/authz/filter.js.map +1 -0
  10. package/dist/authz/index.d.ts +15 -0
  11. package/dist/authz/index.d.ts.map +1 -0
  12. package/dist/authz/index.js +9 -0
  13. package/dist/authz/index.js.map +1 -0
  14. package/dist/authz/permissions.d.ts +20 -0
  15. package/dist/authz/permissions.d.ts.map +1 -0
  16. package/dist/authz/permissions.js +43 -0
  17. package/dist/authz/permissions.js.map +1 -0
  18. package/dist/authz/principals.d.ts +34 -0
  19. package/dist/authz/principals.d.ts.map +1 -0
  20. package/dist/authz/principals.js +42 -0
  21. package/dist/authz/principals.js.map +1 -0
  22. package/dist/authz/reference.d.ts +25 -0
  23. package/dist/authz/reference.d.ts.map +1 -0
  24. package/dist/authz/reference.js +34 -0
  25. package/dist/authz/reference.js.map +1 -0
  26. package/dist/authz/resolve.d.ts +45 -0
  27. package/dist/authz/resolve.d.ts.map +1 -0
  28. package/dist/authz/resolve.js +98 -0
  29. package/dist/authz/resolve.js.map +1 -0
  30. package/dist/authz/scope-tree.d.ts +52 -0
  31. package/dist/authz/scope-tree.d.ts.map +1 -0
  32. package/dist/authz/scope-tree.js +56 -0
  33. package/dist/authz/scope-tree.js.map +1 -0
  34. package/dist/authz/store.d.ts +50 -0
  35. package/dist/authz/store.d.ts.map +1 -0
  36. package/dist/authz/store.js +115 -0
  37. package/dist/authz/store.js.map +1 -0
  38. package/dist/authz/tags.d.ts +40 -0
  39. package/dist/authz/tags.d.ts.map +1 -0
  40. package/dist/authz/tags.js +48 -0
  41. package/dist/authz/tags.js.map +1 -0
  42. package/dist/config.d.ts +73 -0
  43. package/dist/config.d.ts.map +1 -0
  44. package/dist/config.js +157 -0
  45. package/dist/config.js.map +1 -0
  46. package/dist/db/client.d.ts +38 -0
  47. package/dist/db/client.d.ts.map +1 -0
  48. package/dist/db/client.js +34 -0
  49. package/dist/db/client.js.map +1 -0
  50. package/dist/db/migrate.d.ts +32 -0
  51. package/dist/db/migrate.d.ts.map +1 -0
  52. package/dist/db/migrate.js +103 -0
  53. package/dist/db/migrate.js.map +1 -0
  54. package/dist/index.d.ts +16 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +11 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/metrics.d.ts +77 -0
  59. package/dist/metrics.d.ts.map +1 -0
  60. package/dist/metrics.js +179 -0
  61. package/dist/metrics.js.map +1 -0
  62. package/dist/migrate-main.d.ts +2 -0
  63. package/dist/migrate-main.d.ts.map +1 -0
  64. package/dist/migrate-main.js +34 -0
  65. package/dist/migrate-main.js.map +1 -0
  66. package/dist/migrations/0001_init.sql +226 -0
  67. package/dist/migrations/0002_rls_coverage.sql +128 -0
  68. package/dist/migrations/0003_groups_version.sql +48 -0
  69. package/dist/migrations/0004_acl_version.sql +26 -0
  70. package/dist/migrations/0005_grants_bump_version.sql +38 -0
  71. package/dist/migrations/0006_layer_slug_unique.sql +47 -0
  72. package/dist/migrations/0007_index_lease.sql +42 -0
  73. package/dist/observability.d.ts +11 -0
  74. package/dist/observability.d.ts.map +1 -0
  75. package/dist/observability.js +55 -0
  76. package/dist/observability.js.map +1 -0
  77. package/dist/tsconfig.tsbuildinfo +1 -0
  78. package/dist/types.d.ts +54 -0
  79. package/dist/types.d.ts.map +1 -0
  80. package/dist/types.js +15 -0
  81. package/dist/types.js.map +1 -0
  82. package/dist/vector/query.d.ts +140 -0
  83. package/dist/vector/query.d.ts.map +1 -0
  84. package/dist/vector/query.js +94 -0
  85. package/dist/vector/query.js.map +1 -0
  86. package/dist/vector/search.d.ts +62 -0
  87. package/dist/vector/search.d.ts.map +1 -0
  88. package/dist/vector/search.js +98 -0
  89. package/dist/vector/search.js.map +1 -0
  90. 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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"}
@@ -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"}