@anchrd/intel-api 0.6.7 → 0.7.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/README.md +44 -3
- package/dist/adapters/cloudflare/cloudflare.js +50 -16
- package/dist/adapters/cloudflare/cloudflare.types.d.ts +11 -0
- package/dist/adapters/content/content.d.ts +1 -1
- package/dist/adapters/db/db-flows.js +148 -20
- package/dist/adapters/db/db-grants.d.ts +13 -2
- package/dist/adapters/db/db-grants.js +25 -8
- package/dist/adapters/db/db-indexing.d.ts +2 -2
- package/dist/adapters/db/db-indexing.js +26 -19
- package/dist/adapters/db/db.d.ts +3 -3
- package/dist/adapters/db/db.js +431 -118
- package/dist/adapters/gate-applications/gate-applications.d.ts +23 -0
- package/dist/adapters/gate-applications/gate-applications.js +66 -0
- package/dist/adapters/index-queue/index-queue.d.ts +1 -1
- package/dist/adapters/index-queue/index-queue.js +2 -2
- package/dist/adapters/semantic-index/semantic-index.types.d.ts +2 -2
- package/dist/agent-runtime/agent-runtime.d.ts +16 -0
- package/dist/agent-runtime/agent-runtime.js +76 -0
- package/dist/agent-runtime/agent-runtime.types.d.ts +57 -0
- package/dist/bundle/bundle.d.ts +4 -0
- package/dist/bundle/bundle.js +1035 -0
- package/dist/bundle/bundle.types.d.ts +33 -0
- package/dist/bundle/bundle.types.js +1 -0
- package/dist/cli/cli.js +10 -1
- package/dist/flows/flows.d.ts +8 -8
- package/dist/flows/flows.js +158 -42
- package/dist/flows/flows.types.d.ts +40 -7
- package/dist/http/http.d.ts +1 -0
- package/dist/http/http.js +324 -61
- package/dist/http/http.types.d.ts +6 -2
- package/dist/indexing/indexing.js +14 -2
- package/dist/indexing/indexing.types.d.ts +2 -2
- package/dist/intel/intel.js +12 -3
- package/dist/intel/intel.types.d.ts +6 -2
- package/dist/mcp/mcp.js +483 -124
- package/dist/mcp/mcp.types.d.ts +11 -2
- package/dist/nodes/nodes.d.ts +2 -0
- package/dist/nodes/nodes.js +1337 -0
- package/dist/nodes/nodes.types.d.ts +314 -0
- package/dist/nodes/nodes.types.js +1 -0
- package/migrations/0011_one_name_for_the_tree.sql +53 -0
- package/migrations/0012_table_snapshots.sql +29 -0
- package/migrations/0013_agents_in_the_tree.sql +76 -0
- package/migrations/0014_agent_applications.sql +25 -0
- package/package.json +3 -2
- package/dist/knowledge/knowledge.d.ts +0 -2
- package/dist/knowledge/knowledge.js +0 -761
- package/dist/knowledge/knowledge.types.d.ts +0 -198
- /package/dist/{knowledge/knowledge.types.js → agent-runtime/agent-runtime.types.js} +0 -0
- /package/dist/{knowledge → nodes}/document-links/document-links.d.ts +0 -0
- /package/dist/{knowledge → nodes}/document-links/document-links.js +0 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import type { AgentList, AppendTableRowsInput, AppendTableRowsResult, ArchiveNodeInput, CreateAgentInput, CreatedAgent, CreateNodeInput, DefineTableInput, DeleteTableRowsInput, DeleteTableRowsResult, Flow, FlowVersion, GetAgentInput, ListAgentsInput, ListNodesInput, Node, NodeAgent, NodeAttachment, NodeCitation, NodeDocument, NodeGraph, NodeGraphInput, NodeLink, NodeTable, NodeVersion, RedefineTableInput, ResolveNodeLinksInput, ResolveNodeLinksResult, ResourceGrant, ResourceGrantList, ResourceVerb, RevokeGrantInput, SaveAgentDefinitionInput, SaveAttachmentInput, SaveNodeVersionInput, SearchInput, ShareInput, ShareResult, UpdateNodeInput, UpdateTableRowsInput, UpdateTableRowsResult } from "@anchrd/intel-contract";
|
|
2
|
+
import type { SemanticIndex } from "../adapters/semantic-index/semantic-index.types.js";
|
|
3
|
+
export interface Actor {
|
|
4
|
+
id: string;
|
|
5
|
+
email: string;
|
|
6
|
+
isAdmin?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface NewNode {
|
|
9
|
+
node: Node;
|
|
10
|
+
actorId: string;
|
|
11
|
+
idempotencyKey: string;
|
|
12
|
+
auditId: string;
|
|
13
|
+
application?: {
|
|
14
|
+
id: string;
|
|
15
|
+
} | undefined;
|
|
16
|
+
}
|
|
17
|
+
export interface NewNodeVersion {
|
|
18
|
+
version: NodeVersion;
|
|
19
|
+
actorId: string;
|
|
20
|
+
baseVersionId: string | null;
|
|
21
|
+
idempotencyKey: string;
|
|
22
|
+
auditId: string;
|
|
23
|
+
}
|
|
24
|
+
export interface NewTableVersion {
|
|
25
|
+
version: Omit<NodeVersion, "sequence">;
|
|
26
|
+
actorId: string;
|
|
27
|
+
idempotencyKey: string;
|
|
28
|
+
auditId: string;
|
|
29
|
+
}
|
|
30
|
+
export type TableSnapshotOperation = "node.table_update" | "node.table_delete" | "node.table_redefine";
|
|
31
|
+
export interface NewTableSnapshot {
|
|
32
|
+
version: NodeVersion;
|
|
33
|
+
actorId: string;
|
|
34
|
+
baseVersionId: string;
|
|
35
|
+
operation: TableSnapshotOperation;
|
|
36
|
+
metadata: Record<string, unknown>;
|
|
37
|
+
idempotencyKey: string;
|
|
38
|
+
auditId: string;
|
|
39
|
+
}
|
|
40
|
+
export interface NodeRepository {
|
|
41
|
+
listVisible(actor: Actor, input: ListNodesInput): Promise<{
|
|
42
|
+
items: Node[];
|
|
43
|
+
withChildren: string[];
|
|
44
|
+
}>;
|
|
45
|
+
listVisibleBounded(actor: Actor, input: {
|
|
46
|
+
parentId: string | null;
|
|
47
|
+
limit: number;
|
|
48
|
+
}): Promise<BoundedChildren>;
|
|
49
|
+
listVisibleAgents(actor: Actor, input: ListAgentsInput): Promise<Node[]>;
|
|
50
|
+
listVisibleAgentDefinitionKeys(actor: Actor, folderId: string): Promise<string[]>;
|
|
51
|
+
getVisible(actor: Actor, nodeId: string): Promise<Node | null>;
|
|
52
|
+
listVisibleSubtree(actor: Actor, rootId: string | null): Promise<SubtreeNode[]>;
|
|
53
|
+
can(actor: Actor, nodeId: string, verb: ResourceVerb): Promise<boolean>;
|
|
54
|
+
findIdempotentNode(actorId: string, operation: "node.create" | "node.save" | "node.append" | "node.update" | "node.archive" | "node.share" | "node.revoke" | TableSnapshotOperation, idempotencyKey: string): Promise<string | null>;
|
|
55
|
+
findIdempotentRevocation(actorId: string, idempotencyKey: string): Promise<boolean | null>;
|
|
56
|
+
insertNode(input: NewNode): Promise<Node>;
|
|
57
|
+
agentApplicationId(nodeId: string): Promise<string | null>;
|
|
58
|
+
getVersion(versionId: string): Promise<NodeVersion | null>;
|
|
59
|
+
appendVersion(input: NewNodeVersion): Promise<"saved" | "conflict">;
|
|
60
|
+
appendTableVersion(input: NewTableVersion): Promise<NodeVersion>;
|
|
61
|
+
appendTableSnapshot(input: NewTableSnapshot): Promise<"saved" | "conflict">;
|
|
62
|
+
listVersionContentKeys(nodeId: string): Promise<string[]>;
|
|
63
|
+
tableHeaderContentKey(nodeId: string): Promise<string | null>;
|
|
64
|
+
listVersions(nodeId: string): Promise<NodeVersion[]>;
|
|
65
|
+
updateNode(input: {
|
|
66
|
+
node: Node;
|
|
67
|
+
baseUpdatedAt: string;
|
|
68
|
+
actorId: string;
|
|
69
|
+
idempotencyKey: string;
|
|
70
|
+
auditId: string;
|
|
71
|
+
}): Promise<"cycle" | "conflict" | Node>;
|
|
72
|
+
archiveNode(input: {
|
|
73
|
+
nodeId: string;
|
|
74
|
+
baseUpdatedAt: string;
|
|
75
|
+
archivedAt: string | null;
|
|
76
|
+
updatedAt: string;
|
|
77
|
+
actorId: string;
|
|
78
|
+
idempotencyKey: string;
|
|
79
|
+
auditId: string;
|
|
80
|
+
}): Promise<"conflict" | Node>;
|
|
81
|
+
listGrants(resourceId: string): Promise<ResourceGrant[]>;
|
|
82
|
+
organizationExecuteReaches(nodeId: string, exceptGrantId: string): Promise<boolean>;
|
|
83
|
+
listLinksVisible(actor: Actor, nodeId: string): Promise<NodeLink[]>;
|
|
84
|
+
resolveVisibleTitles(actor: Actor, nodeIds: string[]): Promise<Array<{
|
|
85
|
+
nodeId: string;
|
|
86
|
+
title: string;
|
|
87
|
+
}>>;
|
|
88
|
+
replaceTextLinks(input: {
|
|
89
|
+
sourceNodeId: string;
|
|
90
|
+
links: Array<{
|
|
91
|
+
id: string;
|
|
92
|
+
targetNodeId: string;
|
|
93
|
+
}>;
|
|
94
|
+
actorId: string;
|
|
95
|
+
auditId: string;
|
|
96
|
+
occurredAt: string;
|
|
97
|
+
}): Promise<void>;
|
|
98
|
+
graphVisible(actor: Actor, input: NodeGraphInput): Promise<NodeGraph>;
|
|
99
|
+
setGrant(input: {
|
|
100
|
+
grant: ResourceGrant;
|
|
101
|
+
actorId: string;
|
|
102
|
+
idempotencyKey: string;
|
|
103
|
+
auditId: string;
|
|
104
|
+
}): Promise<ResourceGrant>;
|
|
105
|
+
revokeGrant(input: {
|
|
106
|
+
resourceId: string;
|
|
107
|
+
grantId: string;
|
|
108
|
+
actorId: string;
|
|
109
|
+
idempotencyKey: string;
|
|
110
|
+
auditId: string;
|
|
111
|
+
occurredAt: string;
|
|
112
|
+
}): Promise<boolean>;
|
|
113
|
+
searchVisible(actor: Actor, input: SearchInput): Promise<NodeCitation[]>;
|
|
114
|
+
hydrateVisibleCitations(actor: Actor, nodeIds: string[], scopeId?: string): Promise<NodeCitation[]>;
|
|
115
|
+
listCurrentVersionIds(input: {
|
|
116
|
+
after: string | null;
|
|
117
|
+
limit: number;
|
|
118
|
+
}): Promise<string[]>;
|
|
119
|
+
importTree(input: ImportTreeInput): Promise<"created" | "replayed">;
|
|
120
|
+
findImportReplay(actorId: string, idempotencyKey: string): Promise<Record<string, unknown> | null>;
|
|
121
|
+
}
|
|
122
|
+
export interface ImportedLink {
|
|
123
|
+
id: string;
|
|
124
|
+
sourceNodeId: string;
|
|
125
|
+
targetNodeId: string;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Everything one bundle import creates, as one write (#137).
|
|
129
|
+
*
|
|
130
|
+
* ⚠️ The whole point of this shape is the atomicity: every row lands in one `db.batch`, which D1
|
|
131
|
+
* runs as one transaction, so a partial import cannot exist — either the whole subtree stands or
|
|
132
|
+
* nothing does. The R2 objects behind `versions` are written before and deleted again by the
|
|
133
|
+
* caller when the batch refuses.
|
|
134
|
+
*
|
|
135
|
+
* `nodes` must come parents before children: the tree's foreign key checks each row as it lands.
|
|
136
|
+
*/
|
|
137
|
+
export interface ImportTreeInput {
|
|
138
|
+
nodes: Node[];
|
|
139
|
+
versions: NodeVersion[];
|
|
140
|
+
links: ImportedLink[];
|
|
141
|
+
flows: Flow[];
|
|
142
|
+
flowVersions: FlowVersion[];
|
|
143
|
+
actorId: string;
|
|
144
|
+
idempotencyKey: string;
|
|
145
|
+
auditId: string;
|
|
146
|
+
auditResourceId: string;
|
|
147
|
+
metadata: Record<string, unknown>;
|
|
148
|
+
occurredAt: string;
|
|
149
|
+
}
|
|
150
|
+
export interface ContentStore {
|
|
151
|
+
get(key: string): Promise<string | null>;
|
|
152
|
+
getBytes(key: string): Promise<ArrayBuffer | null>;
|
|
153
|
+
getStream(key: string): Promise<ReadableStream<Uint8Array> | null>;
|
|
154
|
+
put(key: string, content: string, mediaType: string): Promise<void>;
|
|
155
|
+
putBytes(key: string, content: ArrayBuffer, mediaType: string): Promise<void>;
|
|
156
|
+
delete(key: string): Promise<void>;
|
|
157
|
+
}
|
|
158
|
+
export interface NodeAttachmentBody {
|
|
159
|
+
attachment: NodeAttachment;
|
|
160
|
+
body: ReadableStream<Uint8Array>;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Gate's Applications surface, as the tree needs it (#182).
|
|
164
|
+
*
|
|
165
|
+
* ⚠️ Every call carries the CALLER's own Gate bearer, never a service key. Creating and switching a
|
|
166
|
+
* machine principal is an administrative act in Gate, gated on `applications:write` and audited
|
|
167
|
+
* there under the person who performed it; Intel's service key does not open that door at all
|
|
168
|
+
* (Gate's two-token rule). The token is therefore a parameter of the operation rather than a field
|
|
169
|
+
* of the actor: it must be impossible to reach for by accident from a service that has no business
|
|
170
|
+
* with it, and it must never end up on an audit row or in a log line.
|
|
171
|
+
*/
|
|
172
|
+
export interface AgentApplications {
|
|
173
|
+
/**
|
|
174
|
+
* A new machine principal, named so a person can recognise the agent behind it.
|
|
175
|
+
*
|
|
176
|
+
* ⚠️ The returned key is plain text, the only time it exists in readable form anywhere, and it is
|
|
177
|
+
* gone from Gate the moment this call returns. It is passed straight back to the caller who asked
|
|
178
|
+
* for it and is written nowhere on the way (D27).
|
|
179
|
+
*/
|
|
180
|
+
create(input: {
|
|
181
|
+
token: string;
|
|
182
|
+
name: string;
|
|
183
|
+
}): Promise<{
|
|
184
|
+
id: string;
|
|
185
|
+
key: string;
|
|
186
|
+
}>;
|
|
187
|
+
setEnabled(input: {
|
|
188
|
+
token: string;
|
|
189
|
+
applicationId: string;
|
|
190
|
+
enabled: boolean;
|
|
191
|
+
}): Promise<void>;
|
|
192
|
+
}
|
|
193
|
+
export interface GateCaller {
|
|
194
|
+
token: string;
|
|
195
|
+
}
|
|
196
|
+
export interface NodesDeps {
|
|
197
|
+
repository: NodeRepository;
|
|
198
|
+
content: ContentStore;
|
|
199
|
+
applications: AgentApplications;
|
|
200
|
+
agentRuntimeAvailable(): boolean;
|
|
201
|
+
id(): string;
|
|
202
|
+
now(): Date;
|
|
203
|
+
externalFlowCallers(actor: Actor, folderId: string): Promise<{
|
|
204
|
+
visible: string[];
|
|
205
|
+
hidden: number;
|
|
206
|
+
}>;
|
|
207
|
+
flowNodeReferences(actor: Actor, folderId: string): Promise<string[]>;
|
|
208
|
+
hash(content: string | Uint8Array): Promise<string>;
|
|
209
|
+
indexing: {
|
|
210
|
+
enqueue(versionId: string): Promise<void>;
|
|
211
|
+
};
|
|
212
|
+
semantic?: SemanticIndex | undefined;
|
|
213
|
+
}
|
|
214
|
+
export type FolderAccess = "ok" | "missing" | "not-a-folder" | "forbidden";
|
|
215
|
+
export interface SubtreeNode {
|
|
216
|
+
node: Node;
|
|
217
|
+
version: {
|
|
218
|
+
id: string;
|
|
219
|
+
mediaType: string;
|
|
220
|
+
contentKey: string;
|
|
221
|
+
} | null;
|
|
222
|
+
}
|
|
223
|
+
export interface BoundedChildren {
|
|
224
|
+
items: Node[];
|
|
225
|
+
total: number;
|
|
226
|
+
}
|
|
227
|
+
export interface NodeService {
|
|
228
|
+
list(actor: Actor, input: ListNodesInput): Promise<{
|
|
229
|
+
items: Node[];
|
|
230
|
+
}>;
|
|
231
|
+
/**
|
|
232
|
+
* One level of the tree as this actor may see it, bounded: at most `limit` current children, and
|
|
233
|
+
* `total` for how many there are. Its caller is the relation graph, which draws a bounded picture
|
|
234
|
+
* and must not read a folder of a thousand documents to do it (#30).
|
|
235
|
+
*
|
|
236
|
+
* ⚠️ `total` counts what passed the visibility predicate and nothing else, or the size a picture
|
|
237
|
+
* reports would become a way of learning that something is filed here.
|
|
238
|
+
*/
|
|
239
|
+
childrenBounded(actor: Actor, input: {
|
|
240
|
+
parentId: string | null;
|
|
241
|
+
limit: number;
|
|
242
|
+
}): Promise<BoundedChildren>;
|
|
243
|
+
get(actor: Actor, nodeId: string): Promise<NodeDocument>;
|
|
244
|
+
getVersion(actor: Actor, nodeId: string, versionId: string): Promise<NodeDocument>;
|
|
245
|
+
folderAccess(actor: Actor, folderId: string): Promise<FolderAccess>;
|
|
246
|
+
create(actor: Actor, input: CreateNodeInput): Promise<Node>;
|
|
247
|
+
save(actor: Actor, input: SaveNodeVersionInput): Promise<NodeDocument>;
|
|
248
|
+
saveAttachment(actor: Actor, input: SaveAttachmentInput): Promise<NodeDocument>;
|
|
249
|
+
getTable(actor: Actor, nodeId: string): Promise<NodeTable>;
|
|
250
|
+
defineTable(actor: Actor, input: DefineTableInput): Promise<NodeTable>;
|
|
251
|
+
appendTableRows(actor: Actor, input: AppendTableRowsInput): Promise<AppendTableRowsResult>;
|
|
252
|
+
updateTableRows(actor: Actor, input: UpdateTableRowsInput): Promise<UpdateTableRowsResult>;
|
|
253
|
+
deleteTableRows(actor: Actor, input: DeleteTableRowsInput): Promise<DeleteTableRowsResult>;
|
|
254
|
+
redefineTable(actor: Actor, input: RedefineTableInput): Promise<NodeTable>;
|
|
255
|
+
getAgent(actor: Actor, input: GetAgentInput): Promise<NodeAgent>;
|
|
256
|
+
/**
|
|
257
|
+
* An agent node, its first definition, and the Gate Application it runs as (#182).
|
|
258
|
+
*
|
|
259
|
+
* ⚠️ The answer carries the Application key ONCE. It is the only value any Intel service returns
|
|
260
|
+
* that is a credential, it is never written down on the way out, and the caller who receives it
|
|
261
|
+
* is the only one who will ever see it.
|
|
262
|
+
*/
|
|
263
|
+
createAgent(actor: Actor, input: CreateAgentInput, caller: GateCaller): Promise<CreatedAgent>;
|
|
264
|
+
saveAgentDefinition(actor: Actor, input: SaveAgentDefinitionInput): Promise<NodeAgent>;
|
|
265
|
+
listAgents(actor: Actor, input: ListAgentsInput): Promise<AgentList>;
|
|
266
|
+
getAttachment(actor: Actor, nodeId: string): Promise<NodeAttachment>;
|
|
267
|
+
readAttachment(actor: Actor, nodeId: string): Promise<NodeAttachmentBody>;
|
|
268
|
+
listVersions(actor: Actor, nodeId: string): Promise<{
|
|
269
|
+
items: NodeVersion[];
|
|
270
|
+
}>;
|
|
271
|
+
update(actor: Actor, input: UpdateNodeInput): Promise<Node>;
|
|
272
|
+
/**
|
|
273
|
+
* Archiving and restoring, for every kind — and for an agent, its Gate Application with it (#182).
|
|
274
|
+
*
|
|
275
|
+
* ⚠️ The Gate call comes FIRST and the node row second. An archived agent whose principal is
|
|
276
|
+
* still live is exactly the access nobody can see any more, so that state must not be reachable
|
|
277
|
+
* by a write that half succeeded; the opposite order can only leave a live agent whose principal
|
|
278
|
+
* is off, which stops working loudly and is repaired by repeating the call.
|
|
279
|
+
*/
|
|
280
|
+
archive(actor: Actor, input: ArchiveNodeInput, caller: GateCaller): Promise<Node>;
|
|
281
|
+
listGrants(actor: Actor, resourceId: string): Promise<ResourceGrantList>;
|
|
282
|
+
listLinks(actor: Actor, nodeId: string): Promise<{
|
|
283
|
+
items: NodeLink[];
|
|
284
|
+
}>;
|
|
285
|
+
resolveLinks(actor: Actor, input: ResolveNodeLinksInput): Promise<ResolveNodeLinksResult>;
|
|
286
|
+
graph(actor: Actor, input: NodeGraphInput): Promise<NodeGraph>;
|
|
287
|
+
visibleNode(actor: Actor, nodeId: string): Promise<Node | null>;
|
|
288
|
+
share(actor: Actor, input: ShareInput): Promise<ShareResult>;
|
|
289
|
+
revokeGrant(actor: Actor, input: RevokeGrantInput): Promise<{
|
|
290
|
+
revoked: boolean;
|
|
291
|
+
}>;
|
|
292
|
+
search(actor: Actor, input: SearchInput): Promise<{
|
|
293
|
+
items: NodeCitation[];
|
|
294
|
+
}>;
|
|
295
|
+
reindex(actor: Actor): Promise<{
|
|
296
|
+
queued: number;
|
|
297
|
+
}>;
|
|
298
|
+
}
|
|
299
|
+
export interface NodeIndexTarget {
|
|
300
|
+
nodeId: string;
|
|
301
|
+
versionId: string;
|
|
302
|
+
title: string;
|
|
303
|
+
description: string | null;
|
|
304
|
+
contentKeys: string[];
|
|
305
|
+
mediaType: string;
|
|
306
|
+
kind: "document" | "attachment" | "table" | "agent";
|
|
307
|
+
updatedAt: string;
|
|
308
|
+
}
|
|
309
|
+
export interface NodeIndexRepository {
|
|
310
|
+
getTarget(versionId: string): Promise<NodeIndexTarget | null>;
|
|
311
|
+
replace(target: NodeIndexTarget, content: string): Promise<void>;
|
|
312
|
+
markIndexed(versionId: string, occurredAt: string): Promise<void>;
|
|
313
|
+
markError(versionId: string, message: string, occurredAt: string): Promise<void>;
|
|
314
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
-- #125: `knowledge` was the first draft's word for the shared tree. The product is called intel,
|
|
2
|
+
-- and a second name for one concept costs a translation in every session. The prefix goes: the tree
|
|
3
|
+
-- is `nodes`, and the four tables around it are named after the node they belong to.
|
|
4
|
+
--
|
|
5
|
+
-- Nothing is rebuilt here, and that is the whole point of the file. `ALTER TABLE ... RENAME TO` is
|
|
6
|
+
-- the one way to change a table's name without a `DROP TABLE`, and `DROP TABLE` is what made the
|
|
7
|
+
-- two migrations that touched this table difficult:
|
|
8
|
+
--
|
|
9
|
+
-- - 0005 had to carry `knowledge_links` out of the way and put it back afterwards, because the
|
|
10
|
+
-- implicit `DELETE FROM` behind `DROP TABLE` fires the ON DELETE CASCADE those rows hang on.
|
|
11
|
+
-- Without the rescue the migration would have committed with every relationship between two
|
|
12
|
+
-- documents quietly gone.
|
|
13
|
+
-- - 0009 gave up on a rebuild altogether. D1 commits the statements of a migration file one at a
|
|
14
|
+
-- time, so `PRAGMA defer_foreign_keys` is spent before the DROP arrives, and
|
|
15
|
+
-- `PRAGMA foreign_keys = OFF` is ignored by D1 over its HTTP API while miniflare honours it —
|
|
16
|
+
-- which is how a green local test can accompany a red remote migration.
|
|
17
|
+
--
|
|
18
|
+
-- A rename touches no row, so neither trap is reachable from here. SQLite rewrites the REFERENCES
|
|
19
|
+
-- clauses of every table pointing at the renamed one — `node_versions`, `node_links` (twice),
|
|
20
|
+
-- `tree_grants`, `flows`, and the tree's reference to itself — so all six foreign keys survive under
|
|
21
|
+
-- the new name and no cascade has anything to fire on. That rewriting is conditional on foreign keys
|
|
22
|
+
-- being enabled, which is exactly what 0005 established the hard way when the cascade took its link
|
|
23
|
+
-- rows: D1 has them on.
|
|
24
|
+
--
|
|
25
|
+
-- ⚠️ Indexes do not follow a rename. They keep working, because an index is bound to its table
|
|
26
|
+
-- rather than to its table's name, but they keep the old name in `sqlite_master` and would be the
|
|
27
|
+
-- last place `knowledge` survives. Dropping and recreating one is free of everything above: an
|
|
28
|
+
-- index holds no rows of its own, and no foreign key points at it.
|
|
29
|
+
--
|
|
30
|
+
-- `node_fts` is a derived FTS5 index and is renamed along with the rest rather than rebuilt; FTS5
|
|
31
|
+
-- renames its own shadow tables. If it ever did come out empty, `intel reindex` builds it back from
|
|
32
|
+
-- D1 and R2 — but nothing here asks it to.
|
|
33
|
+
ALTER TABLE knowledge_nodes RENAME TO nodes;
|
|
34
|
+
ALTER TABLE knowledge_versions RENAME TO node_versions;
|
|
35
|
+
ALTER TABLE knowledge_links RENAME TO node_links;
|
|
36
|
+
ALTER TABLE knowledge_index_state RENAME TO node_index_state;
|
|
37
|
+
ALTER TABLE knowledge_fts RENAME TO node_fts;
|
|
38
|
+
|
|
39
|
+
DROP INDEX knowledge_nodes_parent_idx;
|
|
40
|
+
DROP INDEX knowledge_nodes_owner_idx;
|
|
41
|
+
DROP INDEX knowledge_versions_node_idx;
|
|
42
|
+
DROP INDEX knowledge_links_source_idx;
|
|
43
|
+
DROP INDEX knowledge_links_target_idx;
|
|
44
|
+
DROP INDEX knowledge_links_origin_idx;
|
|
45
|
+
DROP INDEX knowledge_index_state_status_idx;
|
|
46
|
+
|
|
47
|
+
CREATE INDEX nodes_parent_idx ON nodes(parent_id, archived_at, title);
|
|
48
|
+
CREATE INDEX nodes_owner_idx ON nodes(owner_id, archived_at);
|
|
49
|
+
CREATE INDEX node_versions_node_idx ON node_versions(node_id, sequence DESC);
|
|
50
|
+
CREATE INDEX node_links_source_idx ON node_links(source_node_id, created_at);
|
|
51
|
+
CREATE INDEX node_links_target_idx ON node_links(target_node_id, created_at);
|
|
52
|
+
CREATE INDEX node_links_origin_idx ON node_links(source_node_id, origin);
|
|
53
|
+
CREATE INDEX node_index_state_status_idx ON node_index_state(status, updated_at);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
-- #135: a table version says what it carries. An 'append' holds only the rows one write added; a
|
|
2
|
+
-- 'snapshot' holds the complete table — header and every row — so reading starts at the newest
|
|
3
|
+
-- snapshot and everything before it stays history. Update, delete, and redefine write snapshots;
|
|
4
|
+
-- append keeps writing appends; documents and attachments stay NULL because each of their versions
|
|
5
|
+
-- is complete by construction and the word would say nothing about them.
|
|
6
|
+
--
|
|
7
|
+
-- A plain ADD COLUMN, deliberately: unlike 0005 this touches no CHECK an existing column carries,
|
|
8
|
+
-- so nothing has to be rebuilt and no foreign key is ever in flight.
|
|
9
|
+
ALTER TABLE node_versions ADD COLUMN segment TEXT
|
|
10
|
+
CHECK (segment IS NULL OR segment IN ('append', 'snapshot'));
|
|
11
|
+
|
|
12
|
+
-- Every table that exists was defined through `defineTable`, so its first version is the header
|
|
13
|
+
-- segment — the complete state of the moment it was written, which is exactly what a snapshot is.
|
|
14
|
+
-- Marking it so is what lets "read from the newest snapshot" answer for old tables without a
|
|
15
|
+
-- special case for "no snapshot yet". Every later segment of an existing table is an append.
|
|
16
|
+
UPDATE node_versions
|
|
17
|
+
SET segment = CASE
|
|
18
|
+
WHEN sequence = (
|
|
19
|
+
SELECT MIN(inner_version.sequence) FROM node_versions inner_version
|
|
20
|
+
WHERE inner_version.node_id = node_versions.node_id
|
|
21
|
+
) THEN 'snapshot'
|
|
22
|
+
ELSE 'append'
|
|
23
|
+
END
|
|
24
|
+
WHERE node_id IN (SELECT id FROM nodes WHERE kind = 'table');
|
|
25
|
+
|
|
26
|
+
-- The two questions every table read asks — "where is the newest snapshot" and "which segments
|
|
27
|
+
-- follow it" — must not scan the whole history to be answered (#30).
|
|
28
|
+
CREATE INDEX node_versions_segment_idx
|
|
29
|
+
ON node_versions(node_id, segment, sequence DESC);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
-- #139: a fifth kind in the shared tree — `agent`. It is a node like the other four: same
|
|
2
|
+
-- `parent_id`, same folder grants, same immutable `node_versions` rows, same R2 body (ADR-0005 §1).
|
|
3
|
+
-- Its body happens to be a JSON definition rather than prose, which the schema neither knows nor
|
|
4
|
+
-- needs to: nothing here creates a second content model, only the CHECK has to learn the word.
|
|
5
|
+
--
|
|
6
|
+
-- SQLite cannot alter a CHECK constraint, so the table is rebuilt — the same rebuild 0005 performed
|
|
7
|
+
-- for `table`, written the same long way round for the same two reasons. Both detours below are
|
|
8
|
+
-- copied from a file that earned them against a real database, not from caution.
|
|
9
|
+
--
|
|
10
|
+
-- ⚠️ First detour: the new table is created under the final name rather than built beside the old
|
|
11
|
+
-- one and renamed over it. `DROP TABLE` on a parent runs an implicit `DELETE FROM` first, so the
|
|
12
|
+
-- moment the old `nodes` goes, every row of `node_versions`, `tree_grants` and `flows` pointing at a
|
|
13
|
+
-- node is a foreign-key violation. `defer_foreign_keys` postpones the complaint to COMMIT but does
|
|
14
|
+
-- not withdraw it, and `ALTER TABLE ... RENAME` does not settle it either: a rename puts the name
|
|
15
|
+
-- back, not the rows. Only inserting the nodes again, under the name the children have referenced
|
|
16
|
+
-- all along, does. This is also why the self-reference below reads `REFERENCES nodes(id)` — the
|
|
17
|
+
-- final name — which is lesson 2 of the three recorded in `0009_no_context_policy.sql`.
|
|
18
|
+
--
|
|
19
|
+
-- ⚠️ Second detour: `node_links` is the only child of `nodes` declared ON DELETE CASCADE, so that
|
|
20
|
+
-- same implicit delete does not merely flag its rows, it removes them — the migration would commit
|
|
21
|
+
-- with every relationship between two documents quietly gone. The rows are carried out of the way
|
|
22
|
+
-- first and put back afterwards. That is a rescue, not a decision about the data: nothing is
|
|
23
|
+
-- dropped, rewritten or reinterpreted here.
|
|
24
|
+
--
|
|
25
|
+
-- ⚠️ On an empty database neither detour is visible, because nothing points at anything. That is
|
|
26
|
+
-- exactly how 0005's first version passed a green suite and then failed against the first database
|
|
27
|
+
-- with content in it, and why the proof for this file is a row count of every referencing table
|
|
28
|
+
-- before and after rather than a migration that merely ran.
|
|
29
|
+
--
|
|
30
|
+
-- `context_policy` is carried over unchanged and still NOT NULL. It is dead for every consumer
|
|
31
|
+
-- (#76) and only D1's refusal to drop a column a CHECK names keeps it here; removing it is #86 and
|
|
32
|
+
-- deliberately not smuggled into this rebuild.
|
|
33
|
+
PRAGMA defer_foreign_keys = TRUE;
|
|
34
|
+
|
|
35
|
+
-- Plain holding tables on purpose: no keys, no CHECKs, no foreign keys, and the column set taken
|
|
36
|
+
-- from whatever the live table has. Anything enforced here would only be enforced a second time on
|
|
37
|
+
-- the way back in, and a holding table that can reject a row is a holding table that can lose one.
|
|
38
|
+
CREATE TABLE nodes_carry AS SELECT * FROM nodes;
|
|
39
|
+
CREATE TABLE node_links_carry AS SELECT * FROM node_links;
|
|
40
|
+
|
|
41
|
+
DROP TABLE nodes;
|
|
42
|
+
|
|
43
|
+
CREATE TABLE nodes (
|
|
44
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
45
|
+
parent_id TEXT REFERENCES nodes(id),
|
|
46
|
+
kind TEXT NOT NULL CHECK (kind IN ('folder', 'document', 'attachment', 'table', 'agent')),
|
|
47
|
+
title TEXT NOT NULL CHECK (length(title) BETWEEN 1 AND 240),
|
|
48
|
+
description TEXT CHECK (description IS NULL OR length(description) <= 2000),
|
|
49
|
+
context_policy TEXT NOT NULL CHECK (context_policy IN ('pinned', 'relevant', 'explicit')),
|
|
50
|
+
owner_id TEXT NOT NULL,
|
|
51
|
+
current_version_id TEXT,
|
|
52
|
+
created_at TEXT NOT NULL,
|
|
53
|
+
updated_at TEXT NOT NULL,
|
|
54
|
+
archived_at TEXT
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
INSERT INTO nodes (
|
|
58
|
+
id, parent_id, kind, title, description, context_policy, owner_id,
|
|
59
|
+
current_version_id, created_at, updated_at, archived_at
|
|
60
|
+
)
|
|
61
|
+
SELECT
|
|
62
|
+
id, parent_id, kind, title, description, context_policy, owner_id,
|
|
63
|
+
current_version_id, created_at, updated_at, archived_at
|
|
64
|
+
FROM nodes_carry;
|
|
65
|
+
|
|
66
|
+
-- `OR IGNORE` because whether the cascade above actually fired is SQLite's business, not this
|
|
67
|
+
-- migration's: if it did, this puts the rows back; if it did not, each one is already present under
|
|
68
|
+
-- the same primary key and this is a no-op. Either way `node_links` ends up holding exactly what it
|
|
69
|
+
-- held before, which is the only outcome this statement is permitted to have.
|
|
70
|
+
INSERT OR IGNORE INTO node_links SELECT * FROM node_links_carry;
|
|
71
|
+
|
|
72
|
+
DROP TABLE nodes_carry;
|
|
73
|
+
DROP TABLE node_links_carry;
|
|
74
|
+
|
|
75
|
+
CREATE INDEX nodes_parent_idx ON nodes(parent_id, archived_at, title);
|
|
76
|
+
CREATE INDEX nodes_owner_idx ON nodes(owner_id, archived_at);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
-- #182: which Gate Application an agent node runs as. One row per agent that has a principal, and
|
|
2
|
+
-- nothing else — the row is a NAME, never a credential.
|
|
3
|
+
--
|
|
4
|
+
-- ⚠️ The Application KEY has no column here and must never get one (D27). Gate hands a key out once
|
|
5
|
+
-- in plain text and keeps only its hash, so there is nothing to store even in principle; the key
|
|
6
|
+
-- lives in the runtime's `AGENT_APPLICATION_KEYS` secret and reaches it through the one response
|
|
7
|
+
-- that created the agent. A column for it would turn every backup of this database into a set of
|
|
8
|
+
-- machine credentials.
|
|
9
|
+
--
|
|
10
|
+
-- ⚠️ A table rather than a column on `nodes`, for two reasons. The column would be NULL for every
|
|
11
|
+
-- folder, document, attachment and table there will ever be, which is a shape that says nothing
|
|
12
|
+
-- about four of the five kinds; and adding it would mean rebuilding `nodes` — SQLite cannot alter
|
|
13
|
+
-- a table a CHECK constrains — with the two detours `0013_agents_in_the_tree.sql` records. This
|
|
14
|
+
-- file writes one new table and touches nothing that exists.
|
|
15
|
+
--
|
|
16
|
+
-- ⚠️ Deliberately NOT `ON DELETE CASCADE`, unlike `node_links`. Migration 0013 explains what that
|
|
17
|
+
-- one cascade cost the rebuild it had to survive: an implicit `DELETE FROM` on the parent removed
|
|
18
|
+
-- its rows outright rather than merely flagging them, and they had to be carried out of the way and
|
|
19
|
+
-- put back. A second cascading child would hand the next rebuild the same trap twice. Nodes are
|
|
20
|
+
-- archived, never deleted, so nothing is being kept alive by leaving the cascade off.
|
|
21
|
+
CREATE TABLE agent_applications (
|
|
22
|
+
node_id TEXT PRIMARY KEY NOT NULL REFERENCES nodes(id),
|
|
23
|
+
application_id TEXT NOT NULL,
|
|
24
|
+
created_at TEXT NOT NULL
|
|
25
|
+
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anchrd/intel-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -43,9 +43,10 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@anchrd/gate-sdk": "^0.7.0",
|
|
46
|
-
"@anchrd/intel-contract": "^0.
|
|
46
|
+
"@anchrd/intel-contract": "^0.5.0",
|
|
47
47
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
48
48
|
"ajv": "^8.20.0",
|
|
49
|
+
"fflate": "^0.8.3",
|
|
49
50
|
"hono": "^4.12.32",
|
|
50
51
|
"openid-client": "^6.8.4",
|
|
51
52
|
"ulid": "^3.0.2",
|