@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.
Files changed (72) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +9 -0
  3. package/dist/authz/filter.d.ts +54 -3
  4. package/dist/authz/filter.d.ts.map +1 -1
  5. package/dist/authz/filter.js +45 -9
  6. package/dist/authz/filter.js.map +1 -1
  7. package/dist/authz/index.d.ts +2 -2
  8. package/dist/authz/index.d.ts.map +1 -1
  9. package/dist/authz/index.js +1 -1
  10. package/dist/authz/index.js.map +1 -1
  11. package/dist/config.d.ts +33 -0
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/config.js +137 -3
  14. package/dist/config.js.map +1 -1
  15. package/dist/db/client.d.ts +39 -0
  16. package/dist/db/client.d.ts.map +1 -1
  17. package/dist/db/client.js +82 -0
  18. package/dist/db/client.js.map +1 -1
  19. package/dist/db/migrate.d.ts.map +1 -1
  20. package/dist/db/migrate.js +49 -0
  21. package/dist/db/migrate.js.map +1 -1
  22. package/dist/index.d.ts +15 -3
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +9 -3
  25. package/dist/index.js.map +1 -1
  26. package/dist/lifecycle.d.ts +57 -0
  27. package/dist/lifecycle.d.ts.map +1 -0
  28. package/dist/lifecycle.js +97 -0
  29. package/dist/lifecycle.js.map +1 -0
  30. package/dist/metadata.d.ts +50 -0
  31. package/dist/metadata.d.ts.map +1 -0
  32. package/dist/metadata.js +127 -0
  33. package/dist/metadata.js.map +1 -0
  34. package/dist/metrics.d.ts +18 -1
  35. package/dist/metrics.d.ts.map +1 -1
  36. package/dist/metrics.js +77 -2
  37. package/dist/metrics.js.map +1 -1
  38. package/dist/migrations/0008_authenticating_lookup.sql +124 -0
  39. package/dist/migrations/0009_refresh_tokens.sql +73 -0
  40. package/dist/migrations/0010_definer_search_path.sql +22 -0
  41. package/dist/migrations/0011_sweep_lease.sql +36 -0
  42. package/dist/migrations/0012_prune.sql +118 -0
  43. package/dist/migrations/0013_reindex.sql +65 -0
  44. package/dist/oauth.d.ts +45 -0
  45. package/dist/oauth.d.ts.map +1 -0
  46. package/dist/oauth.js +53 -0
  47. package/dist/oauth.js.map +1 -0
  48. package/dist/observability.d.ts.map +1 -1
  49. package/dist/observability.js +81 -13
  50. package/dist/observability.js.map +1 -1
  51. package/dist/passwords.d.ts +37 -0
  52. package/dist/passwords.d.ts.map +1 -0
  53. package/dist/passwords.js +234 -0
  54. package/dist/passwords.js.map +1 -0
  55. package/dist/redis.d.ts +46 -0
  56. package/dist/redis.d.ts.map +1 -0
  57. package/dist/redis.js +234 -0
  58. package/dist/redis.js.map +1 -0
  59. package/dist/reindex.d.ts +87 -0
  60. package/dist/reindex.d.ts.map +1 -0
  61. package/dist/reindex.js +97 -0
  62. package/dist/reindex.js.map +1 -0
  63. package/dist/tsconfig.tsbuildinfo +1 -1
  64. package/dist/vector/query.d.ts +49 -4
  65. package/dist/vector/query.d.ts.map +1 -1
  66. package/dist/vector/query.js +43 -10
  67. package/dist/vector/query.js.map +1 -1
  68. package/dist/vector/search.d.ts +109 -4
  69. package/dist/vector/search.d.ts.map +1 -1
  70. package/dist/vector/search.js +166 -7
  71. package/dist/vector/search.js.map +1 -1
  72. package/package.json +26 -8
@@ -0,0 +1,97 @@
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
+ const log = (service, msg, extra = {}) => {
23
+ console.error(JSON.stringify({ msg, service, ...extra }));
24
+ };
25
+ /**
26
+ * Install the handlers. Returns a `stop` to call from a signal handler.
27
+ *
28
+ * Idempotent: a second SIGTERM while shutting down is ignored rather than
29
+ * starting a second shutdown, because an orchestrator that sends one and then
30
+ * loses patience sends another.
31
+ */
32
+ export function installGuards(guards) {
33
+ const { service, shutdown, graceMs = 10_000, exit = (code) => process.exit(code) } = guards;
34
+ let stopping = false;
35
+ process.on('unhandledRejection', (reason) => {
36
+ // Logged and then rethrown by exiting, rather than swallowed. A promise
37
+ // nobody awaited failing means the process is in a state its author did not
38
+ // plan for, and continuing from there is how one bad request becomes a
39
+ // corrupt one.
40
+ log(service, 'unhandled rejection; exiting', { error: String(reason).slice(0, 500) });
41
+ exit(1);
42
+ });
43
+ process.on('uncaughtException', (error) => {
44
+ log(service, 'uncaught exception; exiting', {
45
+ error: String(error).slice(0, 500),
46
+ stack: error.stack?.split('\n').slice(1, 6).join(' | '),
47
+ });
48
+ exit(1);
49
+ });
50
+ const stop = (signal) => {
51
+ if (stopping)
52
+ return;
53
+ stopping = true;
54
+ log(service, 'shutting down', { signal });
55
+ // The deadline runs whatever happens, and `unref` keeps it from being the
56
+ // thing that holds the process open once everything else has let go.
57
+ const deadline = setTimeout(() => {
58
+ log(service, 'shutdown did not finish in time; exiting anyway', { grace_ms: graceMs });
59
+ exit(1);
60
+ }, graceMs);
61
+ deadline.unref();
62
+ void (async () => {
63
+ try {
64
+ await shutdown?.();
65
+ clearTimeout(deadline);
66
+ exit(0);
67
+ }
68
+ catch (error) {
69
+ log(service, 'shutdown failed', { error: String(error).slice(0, 500) });
70
+ clearTimeout(deadline);
71
+ exit(1);
72
+ }
73
+ })();
74
+ };
75
+ process.on('SIGTERM', () => stop('SIGTERM'));
76
+ process.on('SIGINT', () => stop('SIGINT'));
77
+ return { stop };
78
+ }
79
+ /**
80
+ * Fail a `listen` the way every other startup failure fails.
81
+ *
82
+ * `server.listen(port, cb)` reports a bound port through the callback and a
83
+ * refusal through an `error` event — which arrives after `main()` has resolved,
84
+ * so nothing was catching it.
85
+ */
86
+ export function onListenError(server, service, port, exit = (code) => process.exit(code)) {
87
+ server.on('error', (error) => {
88
+ const detail = error.code === 'EADDRINUSE'
89
+ ? `port ${port} is already in use`
90
+ : error.code === 'EACCES'
91
+ ? `port ${port} needs privileges this process does not have`
92
+ : String(error);
93
+ log(service, 'could not listen', { port, detail });
94
+ exit(2);
95
+ });
96
+ }
97
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../lifecycle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import type { MetadataValue } from './authz/filter.js';
2
+ /**
3
+ * Document metadata: what a caller may write, and what they may narrow a search
4
+ * by.
5
+ *
6
+ * One module for both ends on purpose. Ingest and search accept the same shape
7
+ * from the same untrusted place, and two validators would drift — the ingest
8
+ * side would grow a type the search side could not express, or the search side
9
+ * would accept a key ingest had refused, and a caller would be filtering on a
10
+ * field that can never exist.
11
+ *
12
+ * Everything here is a bound. Metadata is written into the vector payload of
13
+ * *every chunk of a document*, so a key is stored once per point and a
14
+ * thousand-chunk document multiplies whatever arrives here by a thousand. The
15
+ * limits are deliberately small; a knowledge index wants `source`, `team`,
16
+ * `updated_at`, not a document's worth of JSON a second time.
17
+ */
18
+ /** Keys per document. */
19
+ export declare const MAX_METADATA_KEYS = 32;
20
+ export declare const MAX_METADATA_KEY_LENGTH = 64;
21
+ /** A string value, and every string inside a list value. */
22
+ export declare const MAX_METADATA_VALUE_LENGTH = 256;
23
+ /** Values in one list. */
24
+ export declare const MAX_METADATA_LIST = 32;
25
+ export type Metadata = Readonly<Record<string, MetadataValue>>;
26
+ export declare class MetadataError extends Error {
27
+ constructor(message: string);
28
+ }
29
+ /**
30
+ * Validate and normalise metadata arriving from a request.
31
+ *
32
+ * Raises rather than dropping what it does not like. A silently discarded key
33
+ * is a document a caller believes is tagged and a filter that will never match
34
+ * it — the same class of failure as a search parameter read by nothing, and the
35
+ * reason `filters` answered 400 rather than being ignored.
36
+ */
37
+ export declare function parseMetadata(raw: unknown, what?: string): Metadata;
38
+ /**
39
+ * The same bounds, plus the one rule that belongs only to filtering.
40
+ *
41
+ * An empty list is a legitimate *value* — a document may be tagged with no
42
+ * teams — and an impossible *restriction*: "team is one of nothing" matches
43
+ * nothing at all. `buildFilter` refuses to encode it, deliberately, so that
44
+ * what an impossible restriction means stays the caller's decision rather than
45
+ * the query builder's. This is where that decision is made for a request: a 400
46
+ * naming the key, because a client that built a list and put nothing in it has
47
+ * a bug, and answering with an empty result would hide it.
48
+ */
49
+ export declare function parseFilters(raw: unknown): Metadata;
50
+ //# sourceMappingURL=metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEtD;;;;;;;;;;;;;;;GAeG;AAEH,yBAAyB;AACzB,eAAO,MAAM,iBAAiB,KAAK,CAAA;AACnC,eAAO,MAAM,uBAAuB,KAAK,CAAA;AACzC,4DAA4D;AAC5D,eAAO,MAAM,yBAAyB,MAAM,CAAA;AAC5C,0BAA0B;AAC1B,eAAO,MAAM,iBAAiB,KAAK,CAAA;AAanC,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAA;AAE9D,qBAAa,aAAc,SAAQ,KAAK;gBAC1B,OAAO,EAAE,MAAM;CAI5B;AA6BD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,SAAa,GAAG,QAAQ,CAsCvE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,QAAQ,CAWnD"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Document metadata: what a caller may write, and what they may narrow a search
3
+ * by.
4
+ *
5
+ * One module for both ends on purpose. Ingest and search accept the same shape
6
+ * from the same untrusted place, and two validators would drift — the ingest
7
+ * side would grow a type the search side could not express, or the search side
8
+ * would accept a key ingest had refused, and a caller would be filtering on a
9
+ * field that can never exist.
10
+ *
11
+ * Everything here is a bound. Metadata is written into the vector payload of
12
+ * *every chunk of a document*, so a key is stored once per point and a
13
+ * thousand-chunk document multiplies whatever arrives here by a thousand. The
14
+ * limits are deliberately small; a knowledge index wants `source`, `team`,
15
+ * `updated_at`, not a document's worth of JSON a second time.
16
+ */
17
+ /** Keys per document. */
18
+ export const MAX_METADATA_KEYS = 32;
19
+ export const MAX_METADATA_KEY_LENGTH = 64;
20
+ /** A string value, and every string inside a list value. */
21
+ export const MAX_METADATA_VALUE_LENGTH = 256;
22
+ /** Values in one list. */
23
+ export const MAX_METADATA_LIST = 32;
24
+ /**
25
+ * Keys are `[a-z0-9_]`, lower case, and cannot start with a digit.
26
+ *
27
+ * Narrow because the key becomes a Qdrant payload field name, and Qdrant reads
28
+ * `.` as nested access and `[]` as array indexing — a key containing either
29
+ * would filter on something other than what the caller wrote. Refusing the
30
+ * characters is one line; discovering that `a.b` and `a` `{b}` are the same
31
+ * field is a support ticket.
32
+ */
33
+ const KEY = /^[a-z_][a-z0-9_]*$/;
34
+ export class MetadataError extends Error {
35
+ constructor(message) {
36
+ super(message);
37
+ this.name = 'MetadataError';
38
+ }
39
+ }
40
+ function checkScalar(key, value) {
41
+ if (typeof value === 'string') {
42
+ if (value.length > MAX_METADATA_VALUE_LENGTH) {
43
+ throw new MetadataError(`metadata value for '${key}' is over ${MAX_METADATA_VALUE_LENGTH} characters`);
44
+ }
45
+ return value;
46
+ }
47
+ if (typeof value === 'number') {
48
+ // NaN and the infinities have no JSON representation, so they arrive here
49
+ // only from a caller building the body in code — and they would be written
50
+ // as `null`, which is a value nothing can filter for.
51
+ if (!Number.isFinite(value)) {
52
+ throw new MetadataError(`metadata value for '${key}' is not a finite number`);
53
+ }
54
+ return value;
55
+ }
56
+ if (typeof value === 'boolean')
57
+ return value;
58
+ throw new MetadataError(`metadata value for '${key}' must be a string, number, boolean, or a list of those. ` +
59
+ 'Nested objects are not accepted: the filter surface would have to grow a path ' +
60
+ 'syntax, and a path is a way to reach a field the caller did not name.');
61
+ }
62
+ /**
63
+ * Validate and normalise metadata arriving from a request.
64
+ *
65
+ * Raises rather than dropping what it does not like. A silently discarded key
66
+ * is a document a caller believes is tagged and a filter that will never match
67
+ * it — the same class of failure as a search parameter read by nothing, and the
68
+ * reason `filters` answered 400 rather than being ignored.
69
+ */
70
+ export function parseMetadata(raw, what = 'metadata') {
71
+ if (raw === undefined || raw === null)
72
+ return {};
73
+ if (typeof raw !== 'object' || Array.isArray(raw)) {
74
+ throw new MetadataError(`'${what}' must be an object`);
75
+ }
76
+ const entries = Object.entries(raw);
77
+ if (entries.length > MAX_METADATA_KEYS) {
78
+ throw new MetadataError(`'${what}' has more than ${MAX_METADATA_KEYS} keys`);
79
+ }
80
+ const out = {};
81
+ for (const [key, value] of entries) {
82
+ if (key.length > MAX_METADATA_KEY_LENGTH) {
83
+ throw new MetadataError(`metadata key '${key.slice(0, 20)}…' is over ${MAX_METADATA_KEY_LENGTH} characters`);
84
+ }
85
+ if (!KEY.test(key)) {
86
+ throw new MetadataError(`metadata key '${key}' must match ${String(KEY)} — lower case letters, digits and ` +
87
+ 'underscores, not starting with a digit');
88
+ }
89
+ // `undefined` survives an object literal in TypeScript but not JSON, so it
90
+ // reaches here only from a caller assembling the body in code. Dropping it
91
+ // is the one omission that is not a surprise: the key is absent either way.
92
+ if (value === undefined)
93
+ continue;
94
+ if (Array.isArray(value)) {
95
+ if (value.length > MAX_METADATA_LIST) {
96
+ throw new MetadataError(`metadata value for '${key}' has more than ${MAX_METADATA_LIST} entries`);
97
+ }
98
+ out[key] = value.map((v) => checkScalar(key, v));
99
+ }
100
+ else {
101
+ out[key] = checkScalar(key, value);
102
+ }
103
+ }
104
+ return out;
105
+ }
106
+ /**
107
+ * The same bounds, plus the one rule that belongs only to filtering.
108
+ *
109
+ * An empty list is a legitimate *value* — a document may be tagged with no
110
+ * teams — and an impossible *restriction*: "team is one of nothing" matches
111
+ * nothing at all. `buildFilter` refuses to encode it, deliberately, so that
112
+ * what an impossible restriction means stays the caller's decision rather than
113
+ * the query builder's. This is where that decision is made for a request: a 400
114
+ * naming the key, because a client that built a list and put nothing in it has
115
+ * a bug, and answering with an empty result would hide it.
116
+ */
117
+ export function parseFilters(raw) {
118
+ const parsed = parseMetadata(raw, 'filters');
119
+ for (const [key, value] of Object.entries(parsed)) {
120
+ if (Array.isArray(value) && value.length === 0) {
121
+ throw new MetadataError(`filter on '${key}' has no values, so it can match nothing. Drop the key to ` +
122
+ 'search without that restriction.');
123
+ }
124
+ }
125
+ return parsed;
126
+ }
127
+ //# sourceMappingURL=metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.js","sourceRoot":"","sources":["../metadata.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AAEH,yBAAyB;AACzB,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAA;AACnC,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAA;AACzC,4DAA4D;AAC5D,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAA;AAC5C,0BAA0B;AAC1B,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAA;AAEnC;;;;;;;;GAQG;AACH,MAAM,GAAG,GAAG,oBAAoB,CAAA;AAIhC,MAAM,OAAO,aAAc,SAAQ,KAAK;IACtC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,eAAe,CAAA;IAC7B,CAAC;CACF;AAED,SAAS,WAAW,CAAC,GAAW,EAAE,KAAc;IAC9C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,KAAK,CAAC,MAAM,GAAG,yBAAyB,EAAE,CAAC;YAC7C,MAAM,IAAI,aAAa,CACrB,uBAAuB,GAAG,aAAa,yBAAyB,aAAa,CAC9E,CAAA;QACH,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,0EAA0E;QAC1E,2EAA2E;QAC3E,sDAAsD;QACtD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,aAAa,CAAC,uBAAuB,GAAG,0BAA0B,CAAC,CAAA;QAC/E,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAA;IAE5C,MAAM,IAAI,aAAa,CACrB,uBAAuB,GAAG,2DAA2D;QACnF,gFAAgF;QAChF,uEAAuE,CAC1E,CAAA;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,GAAY,EAAE,IAAI,GAAG,UAAU;IAC3D,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,EAAE,CAAA;IAChD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,aAAa,CAAC,IAAI,IAAI,qBAAqB,CAAC,CAAA;IACxD,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAA8B,CAAC,CAAA;IAC9D,IAAI,OAAO,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC;QACvC,MAAM,IAAI,aAAa,CAAC,IAAI,IAAI,mBAAmB,iBAAiB,OAAO,CAAC,CAAA;IAC9E,CAAC;IAED,MAAM,GAAG,GAAkC,EAAE,CAAA;IAC7C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;QACnC,IAAI,GAAG,CAAC,MAAM,GAAG,uBAAuB,EAAE,CAAC;YACzC,MAAM,IAAI,aAAa,CAAC,iBAAiB,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,uBAAuB,aAAa,CAAC,CAAA;QAC9G,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACnB,MAAM,IAAI,aAAa,CACrB,iBAAiB,GAAG,gBAAgB,MAAM,CAAC,GAAG,CAAC,oCAAoC;gBACjF,wCAAwC,CAC3C,CAAA;QACH,CAAC;QACD,2EAA2E;QAC3E,2EAA2E;QAC3E,4EAA4E;QAC5E,IAAI,KAAK,KAAK,SAAS;YAAE,SAAQ;QAEjC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,IAAI,KAAK,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC;gBACrC,MAAM,IAAI,aAAa,CAAC,uBAAuB,GAAG,mBAAmB,iBAAiB,UAAU,CAAC,CAAA;YACnG,CAAC;YACD,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAA;QAClD,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QACpC,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,EAAE,SAAS,CAAC,CAAA;IAC5C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/C,MAAM,IAAI,aAAa,CACrB,cAAc,GAAG,4DAA4D;gBAC3E,kCAAkC,CACrC,CAAA;QACH,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC"}
package/dist/metrics.d.ts CHANGED
@@ -52,7 +52,22 @@ export declare class Registry {
52
52
  * counters — document counts, the propagation lag, pending tombstones.
53
53
  */
54
54
  collect(fn: () => Promise<void>): void;
55
- render(): Promise<string>;
55
+ /**
56
+ * How long a collected value is reused.
57
+ *
58
+ * `/metrics` is unauthenticated by default and the collectors are database
59
+ * queries — one per organization. Without this, anyone who can reach the port
60
+ * decides how often the API queries every tenant's `documents` table, on the
61
+ * same pool the request path uses. A scrape loop is a denial of service that
62
+ * looks like monitoring.
63
+ *
64
+ * Ten seconds is shorter than any sensible scrape interval, so a real
65
+ * Prometheus never sees a cached value and the numbers are as live as they
66
+ * were. It only collapses the excess: a second scraper, a human with `watch
67
+ * curl`, or a flood.
68
+ */
69
+ static readonly COLLECT_TTL_MS = 10000;
70
+ render(now?: number): Promise<string>;
56
71
  }
57
72
  /**
58
73
  * The metrics docs/config.md requires.
@@ -67,9 +82,11 @@ export declare function createMetrics(registry: Registry): {
67
82
  searchDuration: Histogram;
68
83
  searchResults: Counter;
69
84
  aclDenials: Counter;
85
+ authFailures: Counter;
70
86
  aclPropagationLag: Gauge;
71
87
  ingestDuration: Histogram;
72
88
  documents: Gauge;
89
+ reindexProgress: Gauge;
73
90
  tombstonesPending: Gauge;
74
91
  };
75
92
  export type Metrics = ReturnType<typeof createMetrics>;
@@ -1 +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"}
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;IAItC;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,QAAQ,CAAC,cAAc,SAAS;IAwCjC,MAAM,CAAC,GAAG,GAAE,MAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;CAWxD;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ;;;;;;;;;;EAgE/C;AAED,MAAM,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAA"}
package/dist/metrics.js CHANGED
@@ -6,6 +6,7 @@
6
6
  * to keep patched. If the needs here outgrow histograms and gauges, replace it
7
7
  * with prom-client rather than growing this file.
8
8
  */
9
+ var _a;
9
10
  const NAME = /^[a-zA-Z_:][a-zA-Z0-9_:]*$/;
10
11
  function escapeLabel(value) {
11
12
  return value.replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/"/g, '\\"');
@@ -141,10 +142,59 @@ export class Registry {
141
142
  collect(fn) {
142
143
  this.#collectors.push(fn);
143
144
  }
144
- async render() {
145
+ /**
146
+ * How long a collected value is reused.
147
+ *
148
+ * `/metrics` is unauthenticated by default and the collectors are database
149
+ * queries — one per organization. Without this, anyone who can reach the port
150
+ * decides how often the API queries every tenant's `documents` table, on the
151
+ * same pool the request path uses. A scrape loop is a denial of service that
152
+ * looks like monitoring.
153
+ *
154
+ * Ten seconds is shorter than any sensible scrape interval, so a real
155
+ * Prometheus never sees a cached value and the numbers are as live as they
156
+ * were. It only collapses the excess: a second scraper, a human with `watch
157
+ * curl`, or a flood.
158
+ */
159
+ static COLLECT_TTL_MS = 10_000;
160
+ /**
161
+ * When the collectors last finished, on the same clock `render` was given.
162
+ *
163
+ * `undefined` and not 0, because "never collected" and "collected at time
164
+ * zero" have to be different: a caller injecting a clock — a test, or
165
+ * anything replaying — would otherwise find a fresh registry believing it had
166
+ * just collected, and the first scrape would return empty gauges.
167
+ */
168
+ #collectedAt;
169
+ #collecting;
170
+ /**
171
+ * Run the collectors, at most one run at a time and at most one per window.
172
+ *
173
+ * The single-flight part matters as much as the cache: two scrapes arriving
174
+ * together used to start two full sweeps, so concurrency multiplied the cost
175
+ * that the cache alone would bound only in sequence.
176
+ */
177
+ async #collectOnce(now) {
178
+ if (this.#collecting !== undefined)
179
+ return this.#collecting;
180
+ if (this.#collectedAt !== undefined && now - this.#collectedAt < _a.COLLECT_TTL_MS)
181
+ return;
145
182
  // A collector that fails must not blank the whole exposition: the metrics
146
183
  // that still work are how you find out which one broke.
147
- await Promise.allSettled(this.#collectors.map((fn) => fn()));
184
+ this.#collecting = Promise.allSettled(this.#collectors.map((fn) => fn())).then(() => {
185
+ // Stamped with the clock this call was given, never with Date.now().
186
+ // Mixing the two means an injected clock is compared against a real one,
187
+ // and the window is then either always open or never.
188
+ //
189
+ // On completion rather than on entry, so a sweep slower than the window
190
+ // does not have the next one queued behind it the instant it finishes.
191
+ this.#collectedAt = now;
192
+ this.#collecting = undefined;
193
+ });
194
+ return this.#collecting;
195
+ }
196
+ async render(now = Date.now()) {
197
+ await this.#collectOnce(now);
148
198
  const lines = [];
149
199
  for (const metric of this.#metrics) {
150
200
  lines.push(`# HELP ${metric.name} ${metric.help}`);
@@ -154,6 +204,7 @@ export class Registry {
154
204
  return `${lines.join('\n')}\n`;
155
205
  }
156
206
  }
207
+ _a = Registry;
157
208
  /**
158
209
  * The metrics docs/config.md requires.
159
210
  *
@@ -170,9 +221,33 @@ export function createMetrics(registry) {
170
221
  ])),
171
222
  searchResults: registry.register(new Counter('nacre_search_results_total', 'Results returned, by layer')),
172
223
  aclDenials: registry.register(new Counter('nacre_acl_denials_total', 'Access denials, by reason')),
224
+ // Labelled by what was presented, never by why it failed. The 401 itself
225
+ // carries one message for every reason so a caller cannot tell an expired
226
+ // token from a forged one; a `reason` label would hand that distinction
227
+ // back through an endpoint that is unauthenticated by default.
228
+ //
229
+ // `kind="jwt"` is the series a key rotation moves, and it is why this
230
+ // exists: there is no dual-key window, so every outstanding access token
231
+ // fails at once and an operator has to watch that drain. `kind="service_key"`
232
+ // staying flat through the same window is the check that the rotation hit
233
+ // only what it was meant to.
234
+ authFailures: registry.register(new Counter('nacre_auth_failures_total', 'Rejected credentials, by the kind presented: missing, jwt, service_key. Never by reason — the 401 is deliberately one answer')),
173
235
  aclPropagationLag: registry.register(new Gauge('nacre_acl_propagation_lag_seconds', 'Age in seconds of the oldest document, per organization, whose ACL tags predate the current groups_version. Alert on max() over this. The only external evidence that invariant I4 holds')),
174
236
  ingestDuration: registry.register(new Histogram('nacre_ingest_duration_seconds', 'Indexing latency, by stage')),
175
237
  documents: registry.register(new Gauge('nacre_documents_total', 'Documents, by organization and status')),
238
+ // Specified in docs/config.md and registered by nothing until there was a
239
+ // reindex to measure.
240
+ //
241
+ // One series per layer that has ever been reindexed, and no series for the
242
+ // rest — a layer nobody has migrated has no progress to report, and
243
+ // inventing a zero for it would mean every layer in the installation
244
+ // permanently reads "reindex started, gone nowhere".
245
+ //
246
+ // With no reindexes at all the registry still emits a bare
247
+ // `nacre_reindex_progress_ratio 0`, because an unlabelled gauge with no
248
+ // values renders that way here. That is the "nothing to report" marker
249
+ // rather than a layer at zero: a real layer always carries a `layer` label.
250
+ reindexProgress: registry.register(new Gauge('nacre_reindex_progress_ratio', 'How far a layer is through a reindex, 0 to 1. Absent when no reindex has run; 0 means one started and has moved nothing')),
176
251
  tombstonesPending: registry.register(new Gauge('nacre_tombstones_pending_total', 'Deleted documents whose vectors are not yet purged. Climbing means garbage collection is losing')),
177
252
  };
178
253
  }
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../metrics.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../metrics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,124 @@
1
+ -- Resolving a credential cannot be scoped to an organization, because the
2
+ -- credential is what says which organization it is.
3
+ --
4
+ -- `PostgresServiceKeys.resolve` reads `service_accounts` by key prefix across
5
+ -- every tenant for exactly that reason. It has never worked anywhere the
6
+ -- documentation says to deploy it: `org_isolation` calls
7
+ -- `current_setting('app.current_org')` in its one-argument form, which raises
8
+ -- `unrecognized configuration parameter` rather than returning null, so the
9
+ -- query fails outright on any connection that is subject to row-level
10
+ -- security. It works in development only because development connects as a
11
+ -- superuser — the one configuration `docs/config.md` explicitly forbids:
12
+ --
13
+ -- $ psql "postgres://nacre_app@…" -c "SELECT id, org_id FROM service_accounts
14
+ -- WHERE key_prefix = '…'"
15
+ -- ERROR: unrecognized configuration parameter "app.current_org"
16
+ --
17
+ -- So every service account key stops working the moment an operator follows
18
+ -- the rule about not connecting as a superuser. Nothing failed in CI, because
19
+ -- CI connects as `postgres` too.
20
+ --
21
+ -- ─── the fix, and why it is a policy rather than a bypass ───
22
+ --
23
+ -- The obvious repair is a SECURITY DEFINER function, and it does not work
24
+ -- here: these tables carry FORCE ROW LEVEL SECURITY, which applies to the
25
+ -- owner as well, so a function running as the owner is still filtered. The
26
+ -- alternative — a role with BYPASSRLS — buys a permanent hole in the isolation
27
+ -- model to solve a problem that lasts one query.
28
+ --
29
+ -- Row-level security is the mechanism, so this uses it. A second policy admits
30
+ -- the lookup, and only while `app.authenticating` is set on the connection.
31
+ -- Policies for the same command are OR'd, so the org-scoped policy is
32
+ -- unchanged for every other caller.
33
+ --
34
+ -- The guard on `org_isolation` is what makes that safe. Without it Postgres
35
+ -- may evaluate the org-scoped expression anyway and raise before the second
36
+ -- policy is ever consulted; `CASE` short-circuits, so the raising call is not
37
+ -- reached while authenticating. It is deliberately left raising in every other
38
+ -- case: a query that forgets `withOrg` should fail loudly rather than quietly
39
+ -- return nothing, and "quietly returns nothing" is indistinguishable from a
40
+ -- permission working correctly, which is the hardest kind of bug to see.
41
+
42
+ ALTER POLICY org_isolation ON service_accounts
43
+ USING (
44
+ CASE
45
+ WHEN current_setting('app.authenticating', true) = 'on' THEN false
46
+ ELSE org_id = current_setting('app.current_org')::uuid
47
+ END
48
+ );
49
+
50
+ -- SELECT only. Authentication reads a credential; it never writes one, and a
51
+ -- policy that admitted INSERT or UPDATE here would let the one path that runs
52
+ -- without an organization create rows in any of them.
53
+ CREATE POLICY authenticating ON service_accounts
54
+ FOR SELECT
55
+ USING (current_setting('app.authenticating', true) = 'on');
56
+
57
+ -- `users` gets neither, and that is a decision rather than an omission. Login
58
+ -- resolves the organization from `organizations` — which has no row-level
59
+ -- security, because an organization is not inside one — and then reads the
60
+ -- user through `withOrg` like everything else. There is no cross-tenant read
61
+ -- on the login path, so there is nothing here to open.
62
+
63
+ -- ─────────────────────── the worker role that was only ever a comment ───────────────────────
64
+ --
65
+ -- `0001_init.sql` says: "Background worker jobs run under a separate role with
66
+ -- BYPASSRLS." No migration ever created it. The worker claims documents across
67
+ -- every tenant — that is what a queue is — and those queries run on a raw
68
+ -- connection with no organization set, so they raise the same
69
+ -- `unrecognized configuration parameter` as the credential lookup did:
70
+ --
71
+ -- $ psql "postgres://nacre_app@…" -c "SELECT d.id, d.org_id FROM documents d
72
+ -- JOIN organizations o ON o.id = d.org_id"
73
+ -- ERROR: unrecognized configuration parameter "app.current_org"
74
+ --
75
+ -- So indexing stops entirely on a deployment that connects as anything but a
76
+ -- superuser. The Compose stack has never shown it, because POSTGRES_USER makes
77
+ -- the connecting role a superuser — the configuration docs/config.md tells
78
+ -- operators not to use.
79
+ --
80
+ -- BYPASSRLS rather than another policy flag, and the difference from the
81
+ -- lookup above is worth being explicit about: authentication reads one column
82
+ -- set from one table and never writes, so a narrow SELECT policy fits it. The
83
+ -- worker reads and writes documents, chunks and layers across every tenant, so
84
+ -- a flag admitting all of that is BYPASSRLS with a worse name and a longer
85
+ -- audit trail. The honest thing is to say so in the role.
86
+ --
87
+ -- Which is also why the worker names org_id explicitly in every query it makes
88
+ -- afterwards: with the second line of defence off, the first one is all there
89
+ -- is.
90
+
91
+ DO $$
92
+ BEGIN
93
+ IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'nacre_worker') THEN
94
+ CREATE ROLE nacre_worker NOLOGIN BYPASSRLS;
95
+ ELSIF NOT (SELECT rolbypassrls FROM pg_roles WHERE rolname = 'nacre_worker') THEN
96
+ ALTER ROLE nacre_worker BYPASSRLS;
97
+ END IF;
98
+ EXCEPTION WHEN insufficient_privilege THEN
99
+ -- Creating a BYPASSRLS role needs a superuser, and the role running
100
+ -- migrations may not be one. Failing here with the statement to run is the
101
+ -- whole point: the alternative is a worker that starts, claims nothing,
102
+ -- and reports itself healthy.
103
+ RAISE EXCEPTION USING
104
+ MESSAGE = 'nacre_worker could not be created: creating a BYPASSRLS role requires a superuser.',
105
+ HINT = 'Run this once as a superuser, then re-run migrations: '
106
+ 'CREATE ROLE nacre_worker NOLOGIN BYPASSRLS; '
107
+ 'GRANT nacre_worker TO <the role in NACRE_PG_URL>;';
108
+ END
109
+ $$;
110
+
111
+ GRANT USAGE ON SCHEMA public TO nacre_worker;
112
+ GRANT SELECT, INSERT, UPDATE, DELETE ON
113
+ organizations, users, groups, group_members, service_accounts,
114
+ embedding_providers, workspaces, layers, documents, chunks, grants
115
+ TO nacre_worker;
116
+ GRANT SELECT, INSERT ON audit_events TO nacre_worker;
117
+ REVOKE UPDATE, DELETE ON audit_events FROM nacre_worker;
118
+ GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO nacre_worker;
119
+
120
+ -- The application role has to be a member to `SET LOCAL ROLE` to it. Granted
121
+ -- rather than assumed: a deployment that manages roles externally will already
122
+ -- have done this, and one that does not would otherwise get a worker that
123
+ -- connects successfully and cannot claim anything.
124
+ GRANT nacre_worker TO nacre_app;