@anchrd/intel-api 0.5.0 → 0.6.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/db/db.js
CHANGED
|
@@ -89,7 +89,15 @@ export function createKnowledgeRepository(deps) {
|
|
|
89
89
|
* may see and never before them, and `COUNT(*) OVER ()` counts those same rows alone (#30).
|
|
90
90
|
*/
|
|
91
91
|
const visibleChildren = (bounded) => `${visibleCte}
|
|
92
|
-
SELECT ${nodeColumns}${bounded ? ", COUNT(*) OVER () AS total" : ""}
|
|
92
|
+
SELECT ${nodeColumns}${bounded ? ", COUNT(*) OVER () AS total" : ""},
|
|
93
|
+
-- Whether this row has children THIS actor may see (#59), asked of the same allowed set one
|
|
94
|
+
-- level down. A second rule written here would drift from the one above it, and the drift
|
|
95
|
+
-- would show as a chevron that opens onto nothing.
|
|
96
|
+
EXISTS (
|
|
97
|
+
SELECT 1 FROM knowledge_nodes child
|
|
98
|
+
JOIN allowed AS allowed_child ON allowed_child.id = child.id
|
|
99
|
+
WHERE child.parent_id = n.id AND child.archived_at IS NULL
|
|
100
|
+
) AS has_children
|
|
93
101
|
FROM knowledge_nodes n
|
|
94
102
|
JOIN allowed ON allowed.id = n.id
|
|
95
103
|
WHERE n.parent_id IS ? AND (? = 1 OR n.archived_at IS NULL)
|
|
@@ -100,7 +108,11 @@ export function createKnowledgeRepository(deps) {
|
|
|
100
108
|
.prepare(visibleChildren(false))
|
|
101
109
|
.bind(...readBindings(actor), input.parentId, input.includeArchived ? 1 : 0)
|
|
102
110
|
.all();
|
|
103
|
-
|
|
111
|
+
const rows = result.results ?? [];
|
|
112
|
+
return {
|
|
113
|
+
items: rows.map(mapNode),
|
|
114
|
+
withChildren: rows.filter((row) => row.has_children === 1).map((row) => row.id),
|
|
115
|
+
};
|
|
104
116
|
},
|
|
105
117
|
async listVisibleBounded(actor, input) {
|
|
106
118
|
const result = await deps.db
|
|
@@ -151,10 +163,14 @@ export function createKnowledgeRepository(deps) {
|
|
|
151
163
|
try {
|
|
152
164
|
await deps.db.batch([
|
|
153
165
|
deps.db
|
|
166
|
+
// ⚠️ `context_policy` is dead and is written anyway (#76). The column is NOT NULL
|
|
167
|
+
// without a DEFAULT and D1 will not let it be dropped — migration 0009 carries the
|
|
168
|
+
// reason. The fixed value is the price; nothing reads it, and the contract no longer
|
|
169
|
+
// knows the field. When the column goes (anchrd/intel#86), this line goes with it.
|
|
154
170
|
.prepare(`INSERT INTO knowledge_nodes (
|
|
155
|
-
id, parent_id, kind, title, description, owner_id,
|
|
171
|
+
id, parent_id, kind, title, description, context_policy, owner_id,
|
|
156
172
|
current_version_id, created_at, updated_at, archived_at
|
|
157
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
173
|
+
) VALUES (?, ?, ?, ?, ?, 'relevant', ?, ?, ?, ?, ?)`)
|
|
158
174
|
.bind(node.id, node.parentId, node.kind, node.title, node.description, node.ownerId, node.currentVersionId, node.createdAt, node.updatedAt, node.archivedAt),
|
|
159
175
|
deps.db
|
|
160
176
|
.prepare(`INSERT INTO idempotency_keys (
|
package/dist/flows/flows.js
CHANGED
|
@@ -280,6 +280,46 @@ export function createFlows(deps) {
|
|
|
280
280
|
// library folder carries `execute` for everyone and nothing else (ADR-0004 §2/§3), so insisting on
|
|
281
281
|
// `read` here would turn every library flow into a 404 for exactly the people it exists for — and
|
|
282
282
|
// the call rule in section 3 would have nothing left to permit.
|
|
283
|
+
/**
|
|
284
|
+
* Which of these flows call another one that THIS actor may also see (#59).
|
|
285
|
+
*
|
|
286
|
+
* ⚠️ Per reader, and that is the point: a flow whose only call is hidden from this person has
|
|
287
|
+
* nothing to unfold, so a chevron there would promise what opening cannot deliver. `listCalls`
|
|
288
|
+
* filters the same way — asking the same question here, rather than reading the graph and hoping,
|
|
289
|
+
* is the only way the two cannot drift apart.
|
|
290
|
+
*
|
|
291
|
+
* ⚠️ Two reads for the whole level, never one per flow (#30): the versions come back together and
|
|
292
|
+
* the callees go out together.
|
|
293
|
+
*/
|
|
294
|
+
async function callableCallers(actor, flows) {
|
|
295
|
+
const versionOf = new Map();
|
|
296
|
+
for (const flow of flows) {
|
|
297
|
+
const versionId = flow.currentVersionId ?? flow.publishedVersionId;
|
|
298
|
+
if (versionId)
|
|
299
|
+
versionOf.set(flow.id, versionId);
|
|
300
|
+
}
|
|
301
|
+
if (versionOf.size === 0)
|
|
302
|
+
return [];
|
|
303
|
+
const versions = new Map((await deps.repository.getVersions([...new Set(versionOf.values())])).map((version) => [
|
|
304
|
+
version.id,
|
|
305
|
+
version,
|
|
306
|
+
]));
|
|
307
|
+
const wanted = new Map();
|
|
308
|
+
for (const [flowId, versionId] of versionOf) {
|
|
309
|
+
const version = versions.get(versionId);
|
|
310
|
+
if (version)
|
|
311
|
+
wanted.set(flowId, calleeIds(version.graph));
|
|
312
|
+
}
|
|
313
|
+
const everyCallee = [...new Set([...wanted.values()].flat())];
|
|
314
|
+
if (everyCallee.length === 0)
|
|
315
|
+
return [];
|
|
316
|
+
const reachable = new Set((await deps.repository.listCallable(actor, everyCallee))
|
|
317
|
+
.filter((callee) => !callee.archivedAt)
|
|
318
|
+
.map((callee) => callee.id));
|
|
319
|
+
return [...wanted.entries()]
|
|
320
|
+
.filter(([, callees]) => callees.some((callee) => reachable.has(callee)))
|
|
321
|
+
.map(([flowId]) => flowId);
|
|
322
|
+
}
|
|
283
323
|
async function requireRunnableFlow(actor, flowId) {
|
|
284
324
|
const flow = await deps.repository.getCallable(actor, flowId);
|
|
285
325
|
if (!flow || flow.archivedAt)
|
|
@@ -720,7 +760,8 @@ export function createFlows(deps) {
|
|
|
720
760
|
}
|
|
721
761
|
return {
|
|
722
762
|
async list(actor, input = {}) {
|
|
723
|
-
|
|
763
|
+
const items = await deps.repository.listVisible(actor, input);
|
|
764
|
+
return { items, withCalls: await callableCallers(actor, items) };
|
|
724
765
|
},
|
|
725
766
|
async get(actor, flowId) {
|
|
726
767
|
const flow = await requireFlow(actor, flowId);
|
|
@@ -885,7 +926,7 @@ export function createFlows(deps) {
|
|
|
885
926
|
const flow = await requireFlow(actor, flowId);
|
|
886
927
|
const versionId = flow.currentVersionId ?? flow.publishedVersionId;
|
|
887
928
|
if (!versionId)
|
|
888
|
-
return { items: [] };
|
|
929
|
+
return { items: [], withCalls: [] };
|
|
889
930
|
const version = await requireVersion(versionId, flow.id);
|
|
890
931
|
const wanted = calleeIds(version.graph);
|
|
891
932
|
// One read for every callee rather than one per callee (#30). This is what the sidebar asks
|
|
@@ -901,7 +942,8 @@ export function createFlows(deps) {
|
|
|
901
942
|
if (callee && !callee.archivedAt)
|
|
902
943
|
items.push(callee);
|
|
903
944
|
}
|
|
904
|
-
|
|
945
|
+
// Ein aufgerufener Flow ruft selbst welche: dieselbe Frage, eine Ebene tiefer (#59).
|
|
946
|
+
return { items, withCalls: await callableCallers(actor, items) };
|
|
905
947
|
},
|
|
906
948
|
// "What this flow needs", straight out of the graph: no arithmetic, nothing that can go stale,
|
|
907
949
|
// and no claim about whether anyone may reach it. A standing "this flow has conflicts" badge
|
|
@@ -157,10 +157,12 @@ export type FolderAccess = "ok" | "missing" | "not-a-folder" | "forbidden";
|
|
|
157
157
|
export interface FlowService {
|
|
158
158
|
list(actor: FlowActor, input?: ListFlowsInput): Promise<{
|
|
159
159
|
items: Flow[];
|
|
160
|
+
withCalls: string[];
|
|
160
161
|
}>;
|
|
161
162
|
get(actor: FlowActor, flowId: string): Promise<FlowDocument>;
|
|
162
163
|
listCalls(actor: FlowActor, flowId: string): Promise<{
|
|
163
164
|
items: Flow[];
|
|
165
|
+
withCalls: string[];
|
|
164
166
|
}>;
|
|
165
167
|
validate(actor: FlowActor, flowId: string): Promise<FlowValidation>;
|
|
166
168
|
relationGraph(actor: FlowActor, input: RelationGraphInput): Promise<RelationGraph>;
|
|
@@ -286,7 +286,7 @@ export function createKnowledge(deps) {
|
|
|
286
286
|
}
|
|
287
287
|
return {
|
|
288
288
|
async list(actor, input) {
|
|
289
|
-
return
|
|
289
|
+
return await deps.repository.listVisible(actor, input);
|
|
290
290
|
},
|
|
291
291
|
// The same level under a bound, for the one caller that draws a bounded picture of it. It goes
|
|
292
292
|
// through the same predicate as `list`, so what is drawn is a prefix of what is listed and never
|
|
@@ -25,7 +25,10 @@ export interface NewKnowledgeTableVersion {
|
|
|
25
25
|
auditId: string;
|
|
26
26
|
}
|
|
27
27
|
export interface KnowledgeRepository {
|
|
28
|
-
listVisible(actor: Actor, input: ListKnowledgeNodesInput): Promise<
|
|
28
|
+
listVisible(actor: Actor, input: ListKnowledgeNodesInput): Promise<{
|
|
29
|
+
items: KnowledgeNode[];
|
|
30
|
+
withChildren: string[];
|
|
31
|
+
}>;
|
|
29
32
|
listVisibleBounded(actor: Actor, input: {
|
|
30
33
|
parentId: string | null;
|
|
31
34
|
limit: number;
|
|
@@ -1,51 +1,33 @@
|
|
|
1
|
-
-- #76. `context_policy` leaves the
|
|
2
|
-
-- read to decide anything — and it could not have been: it instructed a retrieval Intel does not
|
|
3
|
-
-- perform. Intel hands out references and the agent fetches what it needs (D24).
|
|
1
|
+
-- #76. `context_policy` leaves the contract, the UI and every MCP answer. The COLUMN stays.
|
|
4
2
|
--
|
|
5
|
-
-- ⚠️ SQLite cannot drop a column
|
|
6
|
-
--
|
|
7
|
-
--
|
|
8
|
-
-- it is load-bearing:
|
|
3
|
+
-- ⚠️ That is not the intent, it is what D1 permits. SQLite cannot drop a column a CHECK names, and
|
|
4
|
+
-- this one names itself. The way around it is a table rebuild, and `knowledge_nodes` carries six
|
|
5
|
+
-- foreign keys, one of them from itself.
|
|
9
6
|
--
|
|
10
|
-
--
|
|
11
|
-
--
|
|
12
|
-
--
|
|
13
|
-
--
|
|
14
|
-
--
|
|
15
|
-
--
|
|
16
|
-
--
|
|
17
|
-
--
|
|
18
|
-
--
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
)
|
|
38
|
-
SELECT id, parent_id, kind, title, description, owner_id,
|
|
39
|
-
current_version_id, created_at, updated_at, archived_at
|
|
40
|
-
FROM knowledge_nodes;
|
|
41
|
-
|
|
42
|
-
DROP TABLE knowledge_nodes;
|
|
43
|
-
|
|
44
|
-
ALTER TABLE knowledge_nodes_rebuilt RENAME TO knowledge_nodes;
|
|
45
|
-
|
|
46
|
-
CREATE INDEX knowledge_nodes_parent_idx ON knowledge_nodes(parent_id, archived_at, title);
|
|
47
|
-
CREATE INDEX knowledge_nodes_owner_idx ON knowledge_nodes(owner_id, archived_at);
|
|
48
|
-
|
|
49
|
-
PRAGMA foreign_key_check;
|
|
50
|
-
|
|
51
|
-
PRAGMA foreign_keys = ON;
|
|
7
|
+
-- Three attempts against the real database, three failures, each with its own lesson:
|
|
8
|
+
--
|
|
9
|
+
-- 1. `PRAGMA foreign_keys = OFF` — **ignored** by D1 over its HTTP API. It works locally, because
|
|
10
|
+
-- miniflare honours it, so the integration test was green while production answered
|
|
11
|
+
-- `FOREIGN KEY constraint failed` on the DROP. ⚠️ A green migration test does not prove a D1
|
|
12
|
+
-- migration runs.
|
|
13
|
+
-- 2. `PRAGMA defer_foreign_keys = true` — it works, but the self-reference was written as
|
|
14
|
+
-- `REFERENCES knowledge_nodes(id)` and therefore pointed at the table this same migration was
|
|
15
|
+
-- about to drop. SQLite rewrites a self-reference along with the rename, so the temporary name
|
|
16
|
+
-- belongs there.
|
|
17
|
+
-- 3. Self-reference fixed → green through `wrangler d1 migrations apply --local`, still red
|
|
18
|
+
-- against `--remote`. The reason is the execution model: D1 commits the statements of a
|
|
19
|
+
-- migration file one at a time, and the deferral only lasts until the next COMMIT. After the
|
|
20
|
+
-- first statement the reprieve is gone and `DROP TABLE` runs unprotected.
|
|
21
|
+
--
|
|
22
|
+
-- A table rebuild with foreign keys is therefore not possible inside a migration file. It needs a
|
|
23
|
+
-- session that drives the transaction itself.
|
|
24
|
+
--
|
|
25
|
+
-- What holds instead: the column sits in D1, nothing reads it, and `db.ts` writes a fixed value on
|
|
26
|
+
-- insert because it is NOT NULL without a DEFAULT. The contract does not know it — for every
|
|
27
|
+
-- consumer it is gone. What remains is one dead column, and that is the price of Intel running.
|
|
28
|
+
--
|
|
29
|
+
-- How it disappears after all is anchrd/intel#86.
|
|
30
|
+
--
|
|
31
|
+
-- This file deliberately does nothing. It is where the above is written down; deleted, it would be
|
|
32
|
+
-- a gap in the numbering that nobody explains.
|
|
33
|
+
SELECT 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anchrd/intel-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@anchrd/gate-sdk": "^0.7.0",
|
|
46
|
-
"@anchrd/intel-contract": "^0.
|
|
46
|
+
"@anchrd/intel-contract": "^0.4.0",
|
|
47
47
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
48
48
|
"ajv": "^8.20.0",
|
|
49
49
|
"hono": "^4.12.32",
|