@anchrd/intel-api 0.14.0 → 0.16.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/dist/adapters/cloudflare/cloudflare.js +0 -68
- package/dist/adapters/cloudflare/cloudflare.types.d.ts +0 -39
- package/dist/adapters/db/db-flows.js +3 -2
- package/dist/adapters/db/db-grants.d.ts +1 -1
- package/dist/adapters/db/db-grants.js +1 -1
- package/dist/adapters/db/db.js +16 -113
- package/dist/adapters/openid/openid.js +1 -1
- package/dist/adapters/remote-tools/remote-tools.js +1 -1
- package/dist/bundle/bundle.js +31 -141
- package/dist/bundle/bundle.types.d.ts +1 -1
- package/dist/cli/cli.js +18 -11
- package/dist/flows/flows.d.ts +1 -1
- package/dist/flows/flows.js +1 -1
- package/dist/flows/flows.types.d.ts +4 -1
- package/dist/http/http.js +43 -238
- package/dist/http/http.types.d.ts +0 -8
- package/dist/indexing/indexing.js +18 -89
- package/dist/intel/intel.js +4 -9
- package/dist/intel/intel.types.d.ts +0 -6
- package/dist/mcp/mcp.js +84 -361
- package/dist/mcp/mcp.types.d.ts +2 -7
- package/dist/nodes/document-links/document-links.d.ts +6 -8
- package/dist/nodes/document-links/document-links.js +8 -31
- package/dist/nodes/nodes.js +24 -890
- package/dist/nodes/nodes.types.d.ts +14 -173
- package/dist/tools/tool-servers/tool-servers.d.ts +1 -1
- package/dist/tools/tool-servers/tool-servers.js +1 -1
- package/dist/tools/tools.js +37 -148
- package/dist/tools/tools.types.d.ts +1 -22
- package/migrations/0018_no_context_policy_at_last.sql +13 -6
- package/migrations/0019_one_name_for_the_grants.sql +52 -0
- package/package.json +2 -2
- package/dist/adapters/cloudflare-api/cloudflare-api.d.ts +0 -22
- package/dist/adapters/cloudflare-api/cloudflare-api.js +0 -306
- package/dist/adapters/cloudflare-api/cloudflare-api.types.d.ts +0 -64
- package/dist/adapters/cloudflare-api/cloudflare-api.types.js +0 -1
- package/dist/adapters/gate-applications/gate-applications.d.ts +0 -23
- package/dist/adapters/gate-applications/gate-applications.js +0 -88
- package/dist/adapters/tool-delegation/tool-delegation.d.ts +0 -22
- package/dist/adapters/tool-delegation/tool-delegation.js +0 -90
- package/dist/agent-costs/agent-costs.d.ts +0 -16
- package/dist/agent-costs/agent-costs.js +0 -105
- package/dist/agent-costs/agent-costs.types.d.ts +0 -30
- package/dist/agent-costs/agent-costs.types.js +0 -1
- package/dist/agent-runtime/agent-runtime.d.ts +0 -16
- package/dist/agent-runtime/agent-runtime.js +0 -150
- package/dist/agent-runtime/agent-runtime.types.d.ts +0 -122
- package/dist/agent-runtime/agent-runtime.types.js +0 -1
- package/dist/model-catalog/model-catalog.d.ts +0 -2
- package/dist/model-catalog/model-catalog.js +0 -99
- package/dist/model-catalog/model-catalog.types.d.ts +0 -15
- package/dist/model-catalog/model-catalog.types.js +0 -1
- package/dist/nodes/board/board.d.ts +0 -61
- package/dist/nodes/board/board.js +0 -826
- package/dist/nodes/board/board.types.d.ts +0 -38
- package/dist/nodes/board/board.types.js +0 -1
- package/migrations/0013_agents_in_the_tree.sql +0 -76
- package/migrations/0014_agent_applications.sql +0 -25
- package/migrations/0015_tools_delegated_from_a_connection.sql +0 -15
- package/migrations/0016_boards_in_the_tree.sql +0 -80
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import { createGateClient } from "@anchrd/gate-sdk";
|
|
2
2
|
import { ulid } from "ulid";
|
|
3
|
-
import { createAgentCosts } from "../../agent-costs/agent-costs.js";
|
|
4
|
-
import { createAgentRuntimeService } from "../../agent-runtime/agent-runtime.js";
|
|
5
3
|
import { createBrowserAuth } from "../../auth/auth.js";
|
|
6
4
|
import { createBundle } from "../../bundle/bundle.js";
|
|
7
5
|
import { createFlows } from "../../flows/flows.js";
|
|
8
6
|
import { createIndexing, PermanentIndexingError } from "../../indexing/indexing.js";
|
|
9
7
|
import { createIntel } from "../../intel/intel.js";
|
|
10
|
-
import { createModelCatalog } from "../../model-catalog/model-catalog.js";
|
|
11
8
|
import { createNodes } from "../../nodes/nodes.js";
|
|
12
9
|
import { IntelError } from "../../shared/intel-error/intel-error.js";
|
|
13
10
|
import { sha256Hex } from "../../shared/sha256/sha256.js";
|
|
14
11
|
import { createTools } from "../../tools/tools.js";
|
|
15
|
-
import { createCloudflareApi } from "../cloudflare-api/cloudflare-api.js";
|
|
16
12
|
import { createContentStore } from "../content/content.js";
|
|
17
13
|
import { createNodeRepository } from "../db/db.js";
|
|
18
14
|
import { createFlowRepository } from "../db/db-flows.js";
|
|
@@ -20,7 +16,6 @@ import { createNodeIndexRepository } from "../db/db-indexing.js";
|
|
|
20
16
|
import { createOAuthClientStore } from "../db/db-oauth.js";
|
|
21
17
|
import { createDocumentConverter } from "../document-converter/document-converter.js";
|
|
22
18
|
import { createFlowRuntime } from "../flow-runtime/flow-runtime.js";
|
|
23
|
-
import { createGateApplications } from "../gate-applications/gate-applications.js";
|
|
24
19
|
import { createIndexQueue, IndexMessage } from "../index-queue/index-queue.js";
|
|
25
20
|
import { createJsonSchemaValidator } from "../json-schema/json-schema.js";
|
|
26
21
|
import { createOpenId } from "../openid/openid.js";
|
|
@@ -28,7 +23,6 @@ import { createPortalTokenStore } from "../portal-tokens/portal-tokens.js";
|
|
|
28
23
|
import { createRemoteTools } from "../remote-tools/remote-tools.js";
|
|
29
24
|
import { createSemanticIndex } from "../semantic-index/semantic-index.js";
|
|
30
25
|
import { createSessionCodec } from "../session-cookie/session-cookie.js";
|
|
31
|
-
import { createToolDelegation } from "../tool-delegation/tool-delegation.js";
|
|
32
26
|
import { createToolSourcePolicy } from "../tool-source-policy/tool-source-policy.js";
|
|
33
27
|
// biome-ignore lint/performance/noBarrelFile: The package entry must expose Wrangler's named Workflow class.
|
|
34
28
|
export { IntelFlowWorkflow } from "./cloudflare-flow-workflow.js";
|
|
@@ -82,22 +76,10 @@ export default {
|
|
|
82
76
|
crypto,
|
|
83
77
|
now,
|
|
84
78
|
});
|
|
85
|
-
// Built before the tree since D30: saving an agent has to ask the portal, live, whether the
|
|
86
|
-
// person saving reaches every server they are delegating. The dependency runs one way — the
|
|
87
|
-
// tool service knows nothing about the tree, and the delegation lookup below reads D1 directly
|
|
88
|
-
// rather than through the node service.
|
|
89
|
-
const toolDelegation = createToolDelegation({
|
|
90
|
-
db: env.DB,
|
|
91
|
-
content: contentStore,
|
|
92
|
-
id: ulid,
|
|
93
|
-
now,
|
|
94
|
-
});
|
|
95
79
|
const tools = createTools({
|
|
96
80
|
portalUrl,
|
|
97
81
|
remote: createRemoteTools({ fetch: globalThis.fetch.bind(globalThis) }),
|
|
98
82
|
tokens: portalTokens,
|
|
99
|
-
delegation: toolDelegation.resolve,
|
|
100
|
-
audit: toolDelegation.audit,
|
|
101
83
|
refresh: async (token) => {
|
|
102
84
|
try {
|
|
103
85
|
const refreshed = await oauth.refresh({
|
|
@@ -126,56 +108,9 @@ export default {
|
|
|
126
108
|
return await sha256Hex(crypto, JSON.stringify(value));
|
|
127
109
|
},
|
|
128
110
|
});
|
|
129
|
-
// ⚠️ Built before `nodes` and not after, because creating an agent has to be able to hand the
|
|
130
|
-
// runtime its key inside the same call (D29). Its `visibleNode` closes over `nodes` and is only
|
|
131
|
-
// ever called later, which is what keeps the two from being a cycle.
|
|
132
|
-
const agents = createAgentRuntimeService({
|
|
133
|
-
...(env.AGENT ? { runtime: env.AGENT } : {}),
|
|
134
|
-
...(env.AGENT_HANDOVER_SECRET ? { handoverSecret: env.AGENT_HANDOVER_SECRET } : {}),
|
|
135
|
-
// The tree's own visibility lookup, unchanged: whether somebody may reach an agent is the
|
|
136
|
-
// same question as whether they may see the node, and no second answer is invented here.
|
|
137
|
-
visibleNode: async (actor, nodeId) => await nodes.visibleNode(actor, nodeId),
|
|
138
|
-
});
|
|
139
|
-
// ⚠️ One Cloudflare read client for both of the account's answers — the gateway's cost log
|
|
140
|
-
// (#251) and the Workers AI price list (#257) — because it is one token and one account. It is
|
|
141
|
-
// absent as a whole where any of the three variables is missing: a half-configured client would
|
|
142
|
-
// fail per call with a 401 that reads like a permission problem instead of saying, once, that
|
|
143
|
-
// this deployment was never given a token.
|
|
144
|
-
const gatewayAccountId = env.AI_GATEWAY_ACCOUNT_ID?.trim();
|
|
145
|
-
const gatewayId = env.AI_GATEWAY_ID?.trim();
|
|
146
|
-
const gatewayReadToken = env.AI_GATEWAY_READ_TOKEN?.trim();
|
|
147
|
-
const cloudflareApi = gatewayAccountId && gatewayId && gatewayReadToken
|
|
148
|
-
? createCloudflareApi({
|
|
149
|
-
accountId: gatewayAccountId,
|
|
150
|
-
gatewayId,
|
|
151
|
-
token: gatewayReadToken,
|
|
152
|
-
fetch: globalThis.fetch.bind(globalThis),
|
|
153
|
-
})
|
|
154
|
-
: undefined;
|
|
155
|
-
const agentCosts = createAgentCosts({
|
|
156
|
-
...(cloudflareApi ? { gateway: cloudflareApi } : {}),
|
|
157
|
-
visibleNode: async (actor, nodeId) => await nodes.visibleNode(actor, nodeId),
|
|
158
|
-
now,
|
|
159
|
-
});
|
|
160
|
-
const models = createModelCatalog({ ...(cloudflareApi ? { cloudflare: cloudflareApi } : {}) });
|
|
161
111
|
const nodes = createNodes({
|
|
162
112
|
repository: nodeRepository,
|
|
163
113
|
content: contentStore,
|
|
164
|
-
// ⚠️ Built from `GATE_URL` alone — no service key is handed to it, and none would help. The
|
|
165
|
-
// Applications surface is admin-gated on a real principal, so every call carries the bearer
|
|
166
|
-
// of the person making it, which the tree passes in per operation (#182).
|
|
167
|
-
applications: createGateApplications({
|
|
168
|
-
fetch: globalThis.fetch.bind(globalThis),
|
|
169
|
-
gateUrl: env.GATE_URL,
|
|
170
|
-
}),
|
|
171
|
-
// The binding's presence IS the signal (#190) — no separate configuration option exists.
|
|
172
|
-
agentRuntimeAvailable: () => env.AGENT !== undefined,
|
|
173
|
-
// Where the Application key goes, and the only place it goes (D29). The same service the
|
|
174
|
-
// browser paths are proxied through, so there is one door to the runtime and not two.
|
|
175
|
-
agentKeys: { store: async (input) => await agents.storeKey(input) },
|
|
176
|
-
// The same door again (#214): a definition that was written is a schedule that has to be
|
|
177
|
-
// armed, and the runtime only looks when it is told to.
|
|
178
|
-
agentSchedules: { sync: async (input) => await agents.syncSchedules(input) },
|
|
179
114
|
id: ulid,
|
|
180
115
|
now,
|
|
181
116
|
indexing: createIndexQueue(env.INDEXING),
|
|
@@ -247,9 +182,6 @@ export default {
|
|
|
247
182
|
},
|
|
248
183
|
indexing: createIndexQueue(env.INDEXING),
|
|
249
184
|
}),
|
|
250
|
-
agents,
|
|
251
|
-
agentCosts,
|
|
252
|
-
models,
|
|
253
185
|
auth,
|
|
254
186
|
}).fetch(request);
|
|
255
187
|
},
|
|
@@ -9,17 +9,6 @@ export interface CloudflareEnv {
|
|
|
9
9
|
ASSETS?: {
|
|
10
10
|
fetch(request: Request): Promise<Response>;
|
|
11
11
|
};
|
|
12
|
-
/**
|
|
13
|
-
* The agent runtime Worker, bound as a service.
|
|
14
|
-
*
|
|
15
|
-
* ⚠️ Optional, and it has to be: a deployment may run Intel without agents, and a hard requirement
|
|
16
|
-
* here would make the whole installation fail to start over a feature nobody uses. Where it is
|
|
17
|
-
* absent the agent routes answer 503 by name; where it is present the browser reaches the runtime
|
|
18
|
-
* through Intel and never across a second origin (#178).
|
|
19
|
-
*/
|
|
20
|
-
AGENT?: {
|
|
21
|
-
fetch(request: Request): Promise<Response>;
|
|
22
|
-
};
|
|
23
12
|
DB: D1Database;
|
|
24
13
|
CONTENT: R2Bucket;
|
|
25
14
|
INDEXING: QueueProducer<IndexMessage>;
|
|
@@ -30,34 +19,6 @@ export interface CloudflareEnv {
|
|
|
30
19
|
GATE_SERVICE_KEY: string;
|
|
31
20
|
INTEL_URL: string;
|
|
32
21
|
INTEL_SESSION_SECRET: string;
|
|
33
|
-
/**
|
|
34
|
-
* The shared secret that proves a key handover reached the agent runtime through this Worker
|
|
35
|
-
* (D29, #207). The SAME value the agent Worker holds as `AGENT_HANDOVER_SECRET`.
|
|
36
|
-
*
|
|
37
|
-
* ⚠️ Optional exactly like `AGENT`: a deployment may run Intel without agents, and a hard
|
|
38
|
-
* requirement would make the whole installation fail to start over a feature nobody uses. Where
|
|
39
|
-
* it is absent, creating an agent refuses by name instead of leaving one without a key.
|
|
40
|
-
*/
|
|
41
|
-
AGENT_HANDOVER_SECRET?: string;
|
|
42
|
-
/**
|
|
43
|
-
* Where the AI Gateway lives and how to READ it (#251, #257).
|
|
44
|
-
*
|
|
45
|
-
* ⚠️ `AI_GATEWAY_READ_TOKEN` is not the agent runtime's `AI_GATEWAY_TOKEN` and must never be the
|
|
46
|
-
* same value. That one carries `AI Gateway: Run` and buys inference; this one carries
|
|
47
|
-
* `AI Gateway: Read` — plus `Workers AI: Read` for the model catalog — and buys nothing at all.
|
|
48
|
-
* One credential doing both would put a spending permission into the Worker that only reads.
|
|
49
|
-
*
|
|
50
|
-
* ⚠️ Cloudflare cannot scope `AI Gateway: Read` to a single gateway; it is account-wide, the same
|
|
51
|
-
* limitation `AI Gateway: Run` already had in #239. It is worth knowing when the token is created
|
|
52
|
-
* and is not a reason against the route: it reads and writes nothing.
|
|
53
|
-
*
|
|
54
|
-
* ⚠️ All three optional, together. A deployment that configures none of them gets a cost view that
|
|
55
|
-
* says "not configured" and a model catalog off the built-in table — both named states, neither an
|
|
56
|
-
* error. Intel without a Cloudflare token is a supported installation.
|
|
57
|
-
*/
|
|
58
|
-
AI_GATEWAY_ACCOUNT_ID?: string;
|
|
59
|
-
AI_GATEWAY_ID?: string;
|
|
60
|
-
AI_GATEWAY_READ_TOKEN?: string;
|
|
61
22
|
TOOL_SOURCE_ORIGINS: string;
|
|
62
23
|
MCP_PORTAL_URL?: string;
|
|
63
24
|
ALLOW_INSECURE_OAUTH?: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Flow, FlowGraph,
|
|
1
|
+
import { Flow, FlowGraph, FlowVersion, } from "@anchrd/intel-contract/flow";
|
|
2
|
+
import { FlowRun } from "@anchrd/intel-contract/flow-run";
|
|
2
3
|
import { calleeIds, treeLinkKinds } from "../../flows/flows.js";
|
|
3
4
|
import { flowCallable, flowInSubtree, flowInSubtreeBindings, flowVerbBindings, flowVerbQuery, readableOrRunnableCte, subtreeBindings, subtreeCte, } from "./db-grants.js";
|
|
4
5
|
const flowColumnNames = [
|
|
@@ -219,7 +220,7 @@ export function createFlowRepository(deps) {
|
|
|
219
220
|
SELECT
|
|
220
221
|
EXISTS (SELECT 1 FROM ancestors WHERE id = ?) AS in_subtree,
|
|
221
222
|
EXISTS (
|
|
222
|
-
SELECT 1 FROM
|
|
223
|
+
SELECT 1 FROM node_grants grant_row
|
|
223
224
|
JOIN ancestors ON ancestors.id = grant_row.node_id
|
|
224
225
|
WHERE grant_row.principal_type = 'organization'
|
|
225
226
|
AND grant_row.verb = 'execute'
|
package/dist/adapters/db/db.js
CHANGED
|
@@ -5,7 +5,7 @@ const grantColumns = `id, node_id, principal_type, principal_id, verb, expires_a
|
|
|
5
5
|
created_by, created_at`;
|
|
6
6
|
const linkColumns = `link.id, link.source_node_id, link.target_node_id, link.relation,
|
|
7
7
|
link.origin, link.label, link.created_by, link.created_at`;
|
|
8
|
-
// The first row of each node, in the order they arrive. A chunked kind —
|
|
8
|
+
// The first row of each node, in the order they arrive. A chunked kind — one whose index holds
|
|
9
9
|
// one row per task (#285) — answers a query once per matching card; a citation names a node, so the
|
|
10
10
|
// best of them is the one that gets to speak for it.
|
|
11
11
|
function dedupedByNode(rows) {
|
|
@@ -146,66 +146,7 @@ export function createNodeRepository(deps) {
|
|
|
146
146
|
JOIN allowed ON allowed.id = n.id
|
|
147
147
|
WHERE n.archived_at IS NOT NULL
|
|
148
148
|
ORDER BY n.archived_at DESC, lower(n.title), n.id`;
|
|
149
|
-
/**
|
|
150
|
-
* The agents this actor may see, over the whole tree or under one folder (#139).
|
|
151
|
-
*
|
|
152
|
-
* ⚠️ The same `allowed` set every other read joins against, not a second predicate written for
|
|
153
|
-
* agents. An agent is a node, and the moment its list answers from its own rule it can answer
|
|
154
|
-
* differently from `list` about the very same row.
|
|
155
|
-
*
|
|
156
|
-
* Three scopes, matching the flow list: no clause at all for the whole tree, `IS NULL` for the
|
|
157
|
-
* root level, and one folder by ID.
|
|
158
|
-
*/
|
|
159
|
-
const visibleAgents = (clause, includeArchived) => `${visibleCte}
|
|
160
|
-
SELECT ${nodeColumns}
|
|
161
|
-
FROM nodes n
|
|
162
|
-
JOIN allowed ON allowed.id = n.id
|
|
163
|
-
WHERE n.kind = 'agent' ${includeArchived ? "" : "AND n.archived_at IS NULL"} ${clause}
|
|
164
|
-
ORDER BY lower(n.title), n.id`;
|
|
165
|
-
const agentScopeOf = (parentId) => parentId === undefined
|
|
166
|
-
? { clause: "", bindings: [] }
|
|
167
|
-
: parentId === null
|
|
168
|
-
? { clause: "AND n.parent_id IS NULL", bindings: [] }
|
|
169
|
-
: { clause: "AND n.parent_id = ?", bindings: [parentId] };
|
|
170
149
|
return {
|
|
171
|
-
async listVisibleAgents(actor, input) {
|
|
172
|
-
const scope = agentScopeOf(input.parentId);
|
|
173
|
-
const result = await deps.db
|
|
174
|
-
.prepare(visibleAgents(scope.clause, input.includeArchived === true))
|
|
175
|
-
.bind(...readBindings(actor), ...scope.bindings)
|
|
176
|
-
.all();
|
|
177
|
-
return (result.results ?? []).map(mapNode);
|
|
178
|
-
},
|
|
179
|
-
/**
|
|
180
|
-
* The R2 keys of the current definitions of every agent in this folder's subtree that this
|
|
181
|
-
* actor may see — the input to the share warning (#139, ADR-0005 §2).
|
|
182
|
-
*
|
|
183
|
-
* ⚠️ Keys, never bodies and never titles. Whether a referenced node may be NAMED to whoever is
|
|
184
|
-
* sharing is the tree's question and is asked afterwards through `getVisible`, exactly as the
|
|
185
|
-
* flow side does it. Answering it here would be the second rule that #17 and #19 got wrong.
|
|
186
|
-
*
|
|
187
|
-
* ⚠️ Agents this actor may not see contribute nothing, not even a count. Their references would
|
|
188
|
-
* otherwise be attributed to a folder this actor administers, which discloses that the agent is
|
|
189
|
-
* there at all.
|
|
190
|
-
*/
|
|
191
|
-
async listVisibleAgentDefinitionKeys(actor, folderId) {
|
|
192
|
-
const result = await deps.db
|
|
193
|
-
.prepare(`${visibleCte},
|
|
194
|
-
scope(id) AS (
|
|
195
|
-
SELECT id FROM nodes WHERE id = ?
|
|
196
|
-
UNION
|
|
197
|
-
SELECT child.id FROM nodes child JOIN scope ON child.parent_id = scope.id
|
|
198
|
-
)
|
|
199
|
-
SELECT version.content_key AS content_key
|
|
200
|
-
FROM nodes n
|
|
201
|
-
JOIN allowed ON allowed.id = n.id
|
|
202
|
-
JOIN node_versions version ON version.id = n.current_version_id
|
|
203
|
-
WHERE n.kind = 'agent' AND n.archived_at IS NULL AND n.id IN (SELECT id FROM scope)
|
|
204
|
-
ORDER BY n.id`)
|
|
205
|
-
.bind(...readBindings(actor), folderId)
|
|
206
|
-
.all();
|
|
207
|
-
return (result.results ?? []).map((row) => row.content_key);
|
|
208
|
-
},
|
|
209
150
|
async listVisible(actor, input) {
|
|
210
151
|
const result = await deps.db
|
|
211
152
|
.prepare(input.archivedOnly ? archivedEverywhere : visibleChildren(false))
|
|
@@ -377,7 +318,7 @@ export function createNodeRepository(deps) {
|
|
|
377
318
|
*
|
|
378
319
|
* ⚠️ The idempotency row points at the VERSION, and the audit event points at the node, so the
|
|
379
320
|
* two are joined through the version id the snapshot writer puts into the metadata. That
|
|
380
|
-
* metadata is where a
|
|
321
|
+
* metadata is where a snapshot says which positions it touched and how many it removed —
|
|
381
322
|
* facts a replay cannot recompute from the stored document, because the document is the state
|
|
382
323
|
* AFTER the write. Without this, repeating a delete would answer with a count nobody measured.
|
|
383
324
|
*/
|
|
@@ -439,19 +380,8 @@ export function createNodeRepository(deps) {
|
|
|
439
380
|
return null;
|
|
440
381
|
return row.resource_id.startsWith("1:");
|
|
441
382
|
},
|
|
442
|
-
// ⚠️ Its own statement rather than a column on the node read: four of the five kinds can never
|
|
443
|
-
// have an Application, and the two joins every node read already carries are not going to grow
|
|
444
|
-
// a third for a question only agents ask (#182).
|
|
445
|
-
async agentApplicationId(nodeId) {
|
|
446
|
-
const row = await deps.db
|
|
447
|
-
.prepare("SELECT application_id FROM agent_applications WHERE node_id = ?")
|
|
448
|
-
.bind(nodeId)
|
|
449
|
-
.first();
|
|
450
|
-
return row?.application_id ?? null;
|
|
451
|
-
},
|
|
452
383
|
async insertNode(input) {
|
|
453
384
|
const node = input.node;
|
|
454
|
-
const application = input.application;
|
|
455
385
|
try {
|
|
456
386
|
await deps.db.batch([
|
|
457
387
|
deps.db
|
|
@@ -469,23 +399,7 @@ export function createNodeRepository(deps) {
|
|
|
469
399
|
.prepare(`INSERT INTO audit_events (
|
|
470
400
|
id, actor_id, action, resource_type, resource_id, metadata_json, occurred_at
|
|
471
401
|
) VALUES (?, ?, 'node.create', 'node', ?, ?, ?)`)
|
|
472
|
-
.bind(input.auditId, input.actorId, node.id,
|
|
473
|
-
// ⚠️ The Application ID is deliberately NOT in this metadata, and the key is not
|
|
474
|
-
// within reach of it at all. An audit event is read back by people and by the
|
|
475
|
-
// reindexer; what belongs in it is that an agent was created, not the name of the
|
|
476
|
-
// principal behind it — the mapping row below is where that is kept, once (D27).
|
|
477
|
-
JSON.stringify({ kind: node.kind, parentId: node.parentId }), node.createdAt),
|
|
478
|
-
// Last in the batch, and in the SAME batch: D1 runs a batch as one transaction, so the
|
|
479
|
-
// mapping cannot end up without its node or the node without its mapping. An agent whose
|
|
480
|
-
// principal is recorded nowhere is one that archiving would quietly fail to switch off.
|
|
481
|
-
...(application
|
|
482
|
-
? [
|
|
483
|
-
deps.db
|
|
484
|
-
.prepare(`INSERT INTO agent_applications (node_id, application_id, created_at)
|
|
485
|
-
VALUES (?, ?, ?)`)
|
|
486
|
-
.bind(node.id, application.id, node.createdAt),
|
|
487
|
-
]
|
|
488
|
-
: []),
|
|
402
|
+
.bind(input.auditId, input.actorId, node.id, JSON.stringify({ kind: node.kind, parentId: node.parentId }), node.createdAt),
|
|
489
403
|
]);
|
|
490
404
|
}
|
|
491
405
|
catch (error) {
|
|
@@ -832,7 +746,7 @@ export function createNodeRepository(deps) {
|
|
|
832
746
|
.bind(version.id, version.nodeId, version.sequence, version.contentKey, version.mediaType, version.contentHash, version.size,
|
|
833
747
|
// ⚠️ Taken from the version rather than written in as `'snapshot'`. A table's mutation
|
|
834
748
|
// marks a snapshot, because reading a table starts at the newest one and everything
|
|
835
|
-
// before it is history (#135). A
|
|
749
|
+
// before it is history (#135). A snapshot version carries the whole document by
|
|
836
750
|
// construction, so it marks nothing — the same `null` a document's version carries, and
|
|
837
751
|
// for the same reason (#285).
|
|
838
752
|
version.segment, version.createdBy, version.createdAt, version.nodeId, input.baseVersionId),
|
|
@@ -868,7 +782,7 @@ export function createNodeRepository(deps) {
|
|
|
868
782
|
},
|
|
869
783
|
async listGrants(resourceId) {
|
|
870
784
|
const result = await deps.db
|
|
871
|
-
.prepare(`SELECT ${grantColumns} FROM
|
|
785
|
+
.prepare(`SELECT ${grantColumns} FROM node_grants
|
|
872
786
|
WHERE node_id = ?
|
|
873
787
|
ORDER BY principal_type, principal_id, verb`)
|
|
874
788
|
.bind(resourceId)
|
|
@@ -887,7 +801,7 @@ export function createNodeRepository(deps) {
|
|
|
887
801
|
JOIN ancestors child ON child.parent_id = parent.id
|
|
888
802
|
)
|
|
889
803
|
SELECT 1 AS reaches
|
|
890
|
-
FROM
|
|
804
|
+
FROM node_grants grant_row
|
|
891
805
|
JOIN ancestors ON ancestors.id = grant_row.node_id
|
|
892
806
|
WHERE grant_row.id <> ?
|
|
893
807
|
AND grant_row.principal_type = 'organization'
|
|
@@ -966,17 +880,6 @@ export function createNodeRepository(deps) {
|
|
|
966
880
|
.bind(input.auditId, input.actorId, input.sourceNodeId, JSON.stringify({ targetNodeIds: input.links.map((link) => link.targetNodeId) }), input.occurredAt),
|
|
967
881
|
]);
|
|
968
882
|
},
|
|
969
|
-
async recordAgentKeyRotation(input) {
|
|
970
|
-
// ⚠️ No node row moves, so there is nothing to batch this with — the event IS the write. The
|
|
971
|
-
// key is not among the columns and must not be added to them: audit is metadata, and a row
|
|
972
|
-
// that carried the credential would outlive every other copy of it.
|
|
973
|
-
await deps.db
|
|
974
|
-
.prepare(`INSERT INTO audit_events (
|
|
975
|
-
id, actor_id, action, resource_type, resource_id, metadata_json, occurred_at
|
|
976
|
-
) VALUES (?, ?, 'agent.rotate_key', 'node', ?, ?, ?)`)
|
|
977
|
-
.bind(input.auditId, input.actorId, input.nodeId, JSON.stringify({ applicationId: input.applicationId }), input.occurredAt)
|
|
978
|
-
.run();
|
|
979
|
-
},
|
|
980
883
|
async graphVisible(actor, input) {
|
|
981
884
|
const nodeResult = await deps.db
|
|
982
885
|
.prepare(`${visibleCte}
|
|
@@ -1021,7 +924,7 @@ export function createNodeRepository(deps) {
|
|
|
1021
924
|
deps.db
|
|
1022
925
|
// The verb is part of the key, so re-granting the same verb only refreshes its expiry
|
|
1023
926
|
// and never turns one verb into another.
|
|
1024
|
-
.prepare(`INSERT INTO
|
|
927
|
+
.prepare(`INSERT INTO node_grants (
|
|
1025
928
|
id, node_id, principal_type, principal_id, verb,
|
|
1026
929
|
expires_at, created_by, created_at
|
|
1027
930
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
@@ -1031,7 +934,7 @@ export function createNodeRepository(deps) {
|
|
|
1031
934
|
deps.db
|
|
1032
935
|
.prepare(`INSERT INTO idempotency_keys (
|
|
1033
936
|
actor_id, operation, idempotency_key, resource_id, created_at
|
|
1034
|
-
) SELECT ?, 'node.share', ?, id, ? FROM
|
|
937
|
+
) SELECT ?, 'node.share', ?, id, ? FROM node_grants
|
|
1035
938
|
WHERE node_id = ? AND principal_type = ? AND principal_id = ? AND verb = ?`)
|
|
1036
939
|
.bind(input.actorId, input.idempotencyKey, grant.createdAt, grant.resourceId, principalType, principalId, grant.verb),
|
|
1037
940
|
deps.db
|
|
@@ -1041,7 +944,7 @@ export function createNodeRepository(deps) {
|
|
|
1041
944
|
.bind(input.auditId, input.actorId, grant.resourceId, JSON.stringify({ principalType, verb: grant.verb }), grant.createdAt),
|
|
1042
945
|
]);
|
|
1043
946
|
const stored = await deps.db
|
|
1044
|
-
.prepare(`SELECT ${grantColumns} FROM
|
|
947
|
+
.prepare(`SELECT ${grantColumns} FROM node_grants
|
|
1045
948
|
WHERE node_id = ? AND principal_type = ? AND principal_id = ? AND verb = ?`)
|
|
1046
949
|
.bind(grant.resourceId, principalType, principalId, grant.verb)
|
|
1047
950
|
.first();
|
|
@@ -1057,7 +960,7 @@ export function createNodeRepository(deps) {
|
|
|
1057
960
|
.first();
|
|
1058
961
|
if (replayed) {
|
|
1059
962
|
const row = await deps.db
|
|
1060
|
-
.prepare(`SELECT ${grantColumns} FROM
|
|
963
|
+
.prepare(`SELECT ${grantColumns} FROM node_grants WHERE id = ?`)
|
|
1061
964
|
.bind(replayed.resource_id)
|
|
1062
965
|
.first();
|
|
1063
966
|
if (row)
|
|
@@ -1074,11 +977,11 @@ export function createNodeRepository(deps) {
|
|
|
1074
977
|
actor_id, operation, idempotency_key, resource_id, created_at
|
|
1075
978
|
) SELECT ?, 'node.revoke', ?,
|
|
1076
979
|
(CASE WHEN EXISTS (
|
|
1077
|
-
SELECT 1 FROM
|
|
980
|
+
SELECT 1 FROM node_grants WHERE id = ? AND node_id = ?
|
|
1078
981
|
) THEN '1:' ELSE '0:' END) || ?, ?`)
|
|
1079
982
|
.bind(input.actorId, input.idempotencyKey, input.grantId, input.resourceId, input.grantId, input.occurredAt),
|
|
1080
983
|
deps.db
|
|
1081
|
-
.prepare("DELETE FROM
|
|
984
|
+
.prepare("DELETE FROM node_grants WHERE id = ? AND node_id = ?")
|
|
1082
985
|
.bind(input.grantId, input.resourceId),
|
|
1083
986
|
deps.db
|
|
1084
987
|
.prepare(`INSERT INTO audit_events (
|
|
@@ -1121,10 +1024,10 @@ export function createNodeRepository(deps) {
|
|
|
1121
1024
|
LIMIT ?`)
|
|
1122
1025
|
.bind(...readBindings(actor), ...scopeBindings(input.scopeId), query, input.limit)
|
|
1123
1026
|
.all();
|
|
1124
|
-
// ⚠️ One citation per node, and the FIRST is the one kept.
|
|
1125
|
-
//
|
|
1027
|
+
// ⚠️ One citation per node, and the FIRST is the one kept. The index is CHUNKED — a node
|
|
1028
|
+
// may hold several rows (#285) — so a query matching three of them arrives here three times;
|
|
1126
1029
|
// the rows come back in rank order, so the survivor is the best-matching card and its own
|
|
1127
|
-
// passage is what the reader is shown. Without this, one busy
|
|
1030
|
+
// passage is what the reader is shown. Without this, one busy node could fill a whole page
|
|
1128
1031
|
// of results and push every other node off it.
|
|
1129
1032
|
return dedupedByNode(result.results ?? []).map((row) => ({
|
|
1130
1033
|
nodeId: row.node_id,
|
|
@@ -1139,7 +1042,7 @@ export function createNodeRepository(deps) {
|
|
|
1139
1042
|
},
|
|
1140
1043
|
async hydrateVisibleCitations(actor, hits, scopeId) {
|
|
1141
1044
|
// One entry per node: the caller has already picked the best-scoring chunk of each, so a
|
|
1142
|
-
//
|
|
1045
|
+
// node that answered with four passages arrives as the one passage that answered best.
|
|
1143
1046
|
const unique = [...new Map(hits.map((hit) => [hit.nodeId, hit])).values()].slice(0, 100);
|
|
1144
1047
|
if (unique.length === 0)
|
|
1145
1048
|
return [];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ToolTestResult } from "@anchrd/intel-contract";
|
|
1
|
+
import { ToolTestResult } from "@anchrd/intel-contract/tool";
|
|
2
2
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
3
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
4
4
|
import { CfWorkerJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/cfworker";
|