@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.
- package/LICENSE.md +202 -0
- package/NOTICE +17 -0
- package/README.md +112 -0
- package/dist/cli.js +11998 -0
- package/dist/cli.js.map +1 -0
- package/dist/config-Bl9U789m.d.cts +174 -0
- package/dist/config-Bt9bUQqU.d.ts +174 -0
- package/dist/embeddings-B-jZ42mk.d.cts +67 -0
- package/dist/embeddings-DaSdAZN3.d.ts +67 -0
- package/dist/express.cjs +3173 -0
- package/dist/express.cjs.map +1 -0
- package/dist/express.d.cts +24 -0
- package/dist/express.d.ts +24 -0
- package/dist/express.js +3170 -0
- package/dist/express.js.map +1 -0
- package/dist/fastify.cjs +3184 -0
- package/dist/fastify.cjs.map +1 -0
- package/dist/fastify.d.cts +16 -0
- package/dist/fastify.d.ts +16 -0
- package/dist/fastify.js +3181 -0
- package/dist/fastify.js.map +1 -0
- package/dist/governance-BDkcv4qZ.d.cts +79 -0
- package/dist/governance-XIScatRO.d.ts +79 -0
- package/dist/graph/index.cjs +1428 -0
- package/dist/graph/index.cjs.map +1 -0
- package/dist/graph/index.d.cts +104 -0
- package/dist/graph/index.d.ts +104 -0
- package/dist/graph/index.js +1413 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/hono.cjs +3183 -0
- package/dist/hono.cjs.map +1 -0
- package/dist/hono.d.cts +39 -0
- package/dist/hono.d.ts +39 -0
- package/dist/hono.js +3179 -0
- package/dist/hono.js.map +1 -0
- package/dist/index.cjs +11731 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +851 -0
- package/dist/index.d.ts +851 -0
- package/dist/index.js +11676 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.cjs +181 -0
- package/dist/mcp.cjs.map +1 -0
- package/dist/mcp.d.cts +26 -0
- package/dist/mcp.d.ts +26 -0
- package/dist/mcp.js +179 -0
- package/dist/mcp.js.map +1 -0
- package/dist/migrations/sql/0001.sql +119 -0
- package/dist/migrations/sql/0002_graph.sql +48 -0
- package/dist/migrations/sql/0003_tools.sql +61 -0
- package/dist/migrations/sql/0004_acl_indexes.sql +4 -0
- package/dist/redaction-BmDSWJ7h.d.cts +98 -0
- package/dist/redaction-BmDSWJ7h.d.ts +98 -0
- package/dist/redaction-presidio.cjs +79 -0
- package/dist/redaction-presidio.cjs.map +1 -0
- package/dist/redaction-presidio.d.cts +22 -0
- package/dist/redaction-presidio.d.ts +22 -0
- package/dist/redaction-presidio.js +73 -0
- package/dist/redaction-presidio.js.map +1 -0
- package/dist/router-CrxZ2y_Z.d.ts +82 -0
- package/dist/router-OPgSoYAB.d.cts +82 -0
- package/dist/skills/context-engine/SKILL.md +160 -0
- package/package.json +184 -0
- package/src/migrations/sql/0001.sql +119 -0
- package/src/migrations/sql/0002_graph.sql +48 -0
- package/src/migrations/sql/0003_tools.sql +61 -0
- package/src/migrations/sql/0004_acl_indexes.sql +4 -0
- 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.
|