@promptev/context-engine 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 (68) hide show
  1. package/LICENSE.md +202 -0
  2. package/NOTICE +17 -0
  3. package/README.md +112 -0
  4. package/dist/cli.js +11998 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/config-Bl9U789m.d.cts +174 -0
  7. package/dist/config-Bt9bUQqU.d.ts +174 -0
  8. package/dist/embeddings-B-jZ42mk.d.cts +67 -0
  9. package/dist/embeddings-DaSdAZN3.d.ts +67 -0
  10. package/dist/express.cjs +3173 -0
  11. package/dist/express.cjs.map +1 -0
  12. package/dist/express.d.cts +24 -0
  13. package/dist/express.d.ts +24 -0
  14. package/dist/express.js +3170 -0
  15. package/dist/express.js.map +1 -0
  16. package/dist/fastify.cjs +3184 -0
  17. package/dist/fastify.cjs.map +1 -0
  18. package/dist/fastify.d.cts +16 -0
  19. package/dist/fastify.d.ts +16 -0
  20. package/dist/fastify.js +3181 -0
  21. package/dist/fastify.js.map +1 -0
  22. package/dist/governance-BDkcv4qZ.d.cts +79 -0
  23. package/dist/governance-XIScatRO.d.ts +79 -0
  24. package/dist/graph/index.cjs +1428 -0
  25. package/dist/graph/index.cjs.map +1 -0
  26. package/dist/graph/index.d.cts +104 -0
  27. package/dist/graph/index.d.ts +104 -0
  28. package/dist/graph/index.js +1413 -0
  29. package/dist/graph/index.js.map +1 -0
  30. package/dist/hono.cjs +3183 -0
  31. package/dist/hono.cjs.map +1 -0
  32. package/dist/hono.d.cts +39 -0
  33. package/dist/hono.d.ts +39 -0
  34. package/dist/hono.js +3179 -0
  35. package/dist/hono.js.map +1 -0
  36. package/dist/index.cjs +11731 -0
  37. package/dist/index.cjs.map +1 -0
  38. package/dist/index.d.cts +851 -0
  39. package/dist/index.d.ts +851 -0
  40. package/dist/index.js +11676 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/mcp.cjs +181 -0
  43. package/dist/mcp.cjs.map +1 -0
  44. package/dist/mcp.d.cts +26 -0
  45. package/dist/mcp.d.ts +26 -0
  46. package/dist/mcp.js +179 -0
  47. package/dist/mcp.js.map +1 -0
  48. package/dist/migrations/sql/0001.sql +119 -0
  49. package/dist/migrations/sql/0002_graph.sql +48 -0
  50. package/dist/migrations/sql/0003_tools.sql +61 -0
  51. package/dist/migrations/sql/0004_acl_indexes.sql +4 -0
  52. package/dist/redaction-BmDSWJ7h.d.cts +98 -0
  53. package/dist/redaction-BmDSWJ7h.d.ts +98 -0
  54. package/dist/redaction-presidio.cjs +79 -0
  55. package/dist/redaction-presidio.cjs.map +1 -0
  56. package/dist/redaction-presidio.d.cts +22 -0
  57. package/dist/redaction-presidio.d.ts +22 -0
  58. package/dist/redaction-presidio.js +73 -0
  59. package/dist/redaction-presidio.js.map +1 -0
  60. package/dist/router-CrxZ2y_Z.d.ts +82 -0
  61. package/dist/router-OPgSoYAB.d.cts +82 -0
  62. package/dist/skills/context-engine/SKILL.md +160 -0
  63. package/package.json +184 -0
  64. package/src/migrations/sql/0001.sql +119 -0
  65. package/src/migrations/sql/0002_graph.sql +48 -0
  66. package/src/migrations/sql/0003_tools.sql +61 -0
  67. package/src/migrations/sql/0004_acl_indexes.sql +4 -0
  68. package/src/skills/context-engine/SKILL.md +160 -0
@@ -0,0 +1,160 @@
1
+ ---
2
+ name: context-engine
3
+ description: Use when writing code against the @promptev/context-engine library — retrieval, ACLs/principals, ingestion, redaction, governed tools, or mounting its HTTP/MCP surfaces. Covers the access-control semantics that cause silent data leaks when guessed.
4
+ ---
5
+
6
+ # context-engine (JavaScript / TypeScript)
7
+
8
+ Governed retrieval over your own Postgres. Access control is enforced inside
9
+ the SQL predicate of every retrieval leg, before ranking — not applied to
10
+ results afterwards.
11
+
12
+ Import `@promptev/context-engine`. Full guide: https://promptev.ai/documentation/context-engine/
13
+
14
+ ## The one that leaks data
15
+
16
+ `principals` is a **trichotomy**, and the dangerous value is the one that
17
+ looks like an absence:
18
+
19
+ | value | meaning |
20
+ |---|---|
21
+ | `principals=TRUSTED` | Trusted caller — ACL filtering is **DISABLED**. Returns everything. Import it: `import { TRUSTED } from "@promptev/context-engine"`. |
22
+ | `principals=[]` | Anonymous — matches only documents with no ACL. |
23
+ | `principals=["group:hr", ...]` | Matches unrestricted documents plus any whose ACL overlaps the list. |
24
+ | `principals=null` | **DEPRECATED.** Same as `TRUSTED` today, warns, and raises in 1.0. |
25
+
26
+ **Never pass `null` because a request had no authenticated user.** That is the
27
+ opposite of what it means and it returns the entire corpus. Unauthenticated
28
+ means `[]`.
29
+
30
+ ```ts
31
+ // WRONG — leaks every document to an anonymous caller
32
+ const principals = user ? user.groups : null;
33
+
34
+ // RIGHT
35
+ const principals = user ? user.groups : [];
36
+ ```
37
+
38
+ The reason `null` is being retired: it is falsy, so it is what every accessor
39
+ degrades to — `user.groups || null`, a missing dict key, `user ? user.groups : null`.
40
+ The unauthenticated path silently became the maximum-privilege path. `TRUSTED`
41
+ cannot be produced by a null-coalesce; reaching it requires typing it.
42
+
43
+ Use `TRUSTED` for genuinely trusted in-process callers — a backfill script, an
44
+ admin job — never for anything derived from a request.
45
+
46
+ ```ts
47
+ import { TRUSTED } from "@promptev/context-engine";
48
+
49
+ await engine.search(q, { principals: TRUSTED }); // explicit: skip ACL
50
+ await engine.search(q, { principals: [] }); // anonymous
51
+ await engine.search(q, { principals: user.groups }); // scoped
52
+ ```
53
+
54
+ ## Rules that prevent the other silent failures
55
+
56
+ - **Never read `principals` from a request body or query string.** On remote
57
+ surfaces they are injection-only: derive them server-side from the
58
+ authenticated session. A caller who can name their own principals has no
59
+ access control.
60
+ - **`auth` and `principals` are REQUIRED** on `createHonoRouter`,
61
+ `createExpressRouter`, `createFastifyPlugin` and `createMcpApp`. There is
62
+ no default. If you hit a `TypeError`, supply real ones — do **not** silence
63
+ it with `principals: () => null`, which disables access control on a public
64
+ port. Anonymous is `() => []`. Trusted is `() => TRUSTED`.
65
+ - **`acl=null` on `updateDocument` UNRESTRICTS the document.** "Leave it
66
+ alone" is expressed by omitting the argument (the `UNSET` sentinel), because
67
+ `null` already means unrestricted and cannot also mean absent. Same for
68
+ `metaData`, which merges rather than replaces.
69
+ - **Requires pgvector 0.8 or newer.** Below that, ACL-filtered vector search
70
+ silently loses recall — queries return fewer rows than match, or none, with
71
+ no error. The library cannot compensate.
72
+ - **`config.enableCodeExecution` is `false` on purpose.** `engine.compute()`
73
+ executes generated code. Turn it on only deliberately.
74
+ - Filing a document under an ACL the caller does not hold returns **403**, on
75
+ ingest and on PATCH alike.
76
+
77
+ ## Search
78
+
79
+ ```ts
80
+ const result = await engine.search("how much annual leave do I get", {
81
+ principals: ["group:hr"], // from the session, never the request body
82
+ sourceIds: ["hr-handbook"], // optional scope
83
+ topK: 8,
84
+ });
85
+ for (const hit of result.hits) {
86
+ console.log(hit.chunkText, hit.meta);
87
+ }
88
+ ```
89
+
90
+ Hybrid full-text + trigram + vector, fused with RRF. `mode: "graph"` switches
91
+ to the graph leg when the graph subsystem is configured.
92
+
93
+ ## Ingest
94
+
95
+ ```ts
96
+ const report = await engine.ingest({
97
+ content: fs.readFileSync("handbook.pdf"),
98
+ filename: "handbook.pdf",
99
+ sourceId: "hr-handbook",
100
+ acl: ["group:hr"], // omit for an unrestricted document
101
+ });
102
+ ```
103
+
104
+ Accepts `file`, `content` + `filename`, or `text`. Re-ingesting identical
105
+ content is deduplicated by hash; ACL changes on re-ingest are applied.
106
+
107
+ ## Mounting the HTTP surface
108
+
109
+ Framework adapters live on **subpath exports**, not the main package entry
110
+ (so `import "@promptev/context-engine"` never pulls Hono/Express/Fastify):
111
+
112
+ ```ts
113
+ import { createHonoRouter } from "@promptev/context-engine/hono";
114
+ import { TRUSTED } from "@promptev/context-engine";
115
+
116
+ app.route(
117
+ "/context",
118
+ createHonoRouter(engine, {
119
+ auth: async (c) => { /* raise / return 401 on anonymous */ },
120
+ principals: (c) => c.get("user")?.groups ?? [],
121
+ }),
122
+ );
123
+ ```
124
+
125
+ `createExpressRouter` (`@promptev/context-engine/express`),
126
+ `createFastifyPlugin` (`@promptev/context-engine/fastify`) and
127
+ `createMcpApp` (`@promptev/context-engine/mcp`) take the same two
128
+ arguments. MCP has no request object: `principals` is a zero-argument
129
+ callable resolved fresh on every tool call. The CLI `context-engine mcp`
130
+ binds `127.0.0.1` and answers as anonymous (`principals=[]`).
131
+
132
+ Not on a supported framework? Call `engine.search()` / `engine.ingest()`
133
+ from your own handler — the routers are thin wrappers over the same core.
134
+
135
+ ## Lifecycle
136
+
137
+ Construction is lazy; close the engine to tear down only what was built. A
138
+ process-lifetime singleton can skip it, but anything that builds engines
139
+ repeatedly (worker per job, test per case) must close them or it accumulates
140
+ connection pools.
141
+
142
+ ## Redaction
143
+
144
+ Rules with `applyAt: "ingest"` run **before chunks are embedded**, so masked
145
+ text is what reaches the embedding provider. `applyAt: "output"` masks on the
146
+ way out and leaves stored text intact. Use `unless: [...]` to let named
147
+ principals see through a rule.
148
+
149
+ Presidio NER is an HTTP adapter (`@promptev/context-engine/presidio`): set
150
+ `CE_PRESIDIO_URL` or `PRESIDIO_URL` to a Presidio Analyzer endpoint.
151
+
152
+ ## Don't
153
+
154
+ - Don't add a `WHERE` clause of your own on top of a retrieval call to filter
155
+ by tenant — pass `principals`/`sourceIds` and let it go into the indexed
156
+ predicate. Filtering after the fact is what loses recall.
157
+ - Don't cache a `SearchResult` across users. Results are ACL-scoped to the
158
+ caller who requested them.
159
+ - Don't expect `engine.search()` to enforce anything about *writes*; ingest
160
+ and PATCH authorisation is separate and already handled.