@indexnetwork/protocol 14.3.2-rc.477.1 → 16.1.1-rc.478.1
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/CHANGELOG.md +107 -0
- package/IMPLEMENTATION.md +50 -52
- package/STABILITY.md +3 -3
- package/dist/chat/chat.agent.js +2 -2
- package/dist/chat/chat.graph.d.ts +41 -1
- package/dist/chat/chat.graph.js +108 -127
- package/dist/discovery/hyde.frame.d.ts +4 -4
- package/dist/discovery/hyde.graph.d.ts +71 -12
- package/dist/discovery/hyde.graph.js +337 -335
- package/dist/discovery/lens.inferrer.d.ts +6 -6
- package/dist/enrichment/enrichment.graph.d.ts +127 -9
- package/dist/enrichment/enrichment.graph.js +633 -655
- package/dist/enrichment/enrichment.state.d.ts +1 -1
- package/dist/enrichment/enrichment.tools.context-read.d.ts +10 -0
- package/dist/enrichment/enrichment.tools.context-read.js +353 -0
- package/dist/enrichment/enrichment.tools.context-write.d.ts +9 -0
- package/dist/enrichment/enrichment.tools.context-write.js +417 -0
- package/dist/enrichment/enrichment.tools.d.ts +11 -1
- package/dist/enrichment/enrichment.tools.helpers.d.ts +139 -0
- package/dist/enrichment/enrichment.tools.helpers.js +234 -0
- package/dist/enrichment/enrichment.tools.js +16 -975
- package/dist/index.d.ts +27 -48
- package/dist/index.js +17 -27
- package/dist/intents/application/intent.graph.d.ts +71 -67
- package/dist/intents/application/intent.graph.execute.d.ts +59 -0
- package/dist/intents/application/intent.graph.execute.js +301 -0
- package/dist/intents/application/intent.graph.infer.d.ts +44 -0
- package/dist/intents/application/intent.graph.infer.js +97 -0
- package/dist/intents/application/intent.graph.js +96 -888
- package/dist/intents/application/intent.graph.reconcile.d.ts +71 -0
- package/dist/intents/application/intent.graph.reconcile.js +274 -0
- package/dist/intents/application/intent.graph.shared.d.ts +70 -0
- package/dist/intents/application/intent.graph.shared.js +153 -0
- package/dist/intents/domain/intent.state.d.ts +1 -1
- package/dist/maintenance/maintenance.graph.d.ts +66 -3
- package/dist/maintenance/maintenance.graph.js +155 -156
- package/dist/mcp/mcp.authorization-policy.d.ts +24 -28
- package/dist/mcp/mcp.authorization-policy.js +14 -34
- package/dist/mcp/mcp.server.d.ts +3 -3
- package/dist/mcp/mcp.server.js +12 -16
- package/dist/negotiations/application/negotiation.candidates.d.ts +83 -0
- package/dist/negotiations/application/negotiation.candidates.js +162 -0
- package/dist/negotiations/application/negotiation.graph.d.ts +91 -157
- package/dist/negotiations/application/negotiation.graph.finalize.d.ts +5 -0
- package/dist/negotiations/application/negotiation.graph.finalize.js +280 -0
- package/dist/negotiations/application/negotiation.graph.init.d.ts +54 -0
- package/dist/negotiations/application/negotiation.graph.init.js +227 -0
- package/dist/negotiations/application/negotiation.graph.js +69 -1388
- package/dist/negotiations/application/negotiation.graph.screen.d.ts +23 -0
- package/dist/negotiations/application/negotiation.graph.screen.js +108 -0
- package/dist/negotiations/application/negotiation.graph.shared.d.ts +75 -0
- package/dist/negotiations/application/negotiation.graph.shared.js +125 -0
- package/dist/negotiations/application/negotiation.graph.turn.d.ts +105 -0
- package/dist/negotiations/application/negotiation.graph.turn.js +484 -0
- package/dist/negotiations/domain/negotiation.state.d.ts +1 -1
- package/dist/negotiations/domain/negotiation.state.js +0 -1
- package/dist/networks/application/indexer.graph.d.ts +165 -7
- package/dist/networks/application/indexer.graph.js +339 -388
- package/dist/networks/application/indexer.state.d.ts +1 -1
- package/dist/networks/application/membership.graph.d.ts +83 -5
- package/dist/networks/application/membership.graph.js +177 -207
- package/dist/networks/application/network.graph.d.ts +148 -5
- package/dist/networks/application/network.graph.js +249 -278
- package/dist/networks/domain/membership.state.d.ts +1 -1
- package/dist/networks/domain/network.state.d.ts +1 -1
- package/dist/opportunities/application/delivery-card.cache.d.ts +1 -1
- package/dist/opportunities/application/delivery-card.cache.js +2 -2
- package/dist/opportunities/application/index.d.ts +2 -2
- package/dist/opportunities/application/index.js +2 -2
- package/dist/opportunities/application/opportunity.evaluator.js +1 -1
- package/dist/opportunities/application/opportunity.graph.d.ts +686 -473
- package/dist/opportunities/application/opportunity.graph.discovery-strategies.d.ts +109 -0
- package/dist/opportunities/application/opportunity.graph.discovery-strategies.js +451 -0
- package/dist/opportunities/application/opportunity.graph.discovery.d.ts +69 -0
- package/dist/opportunities/application/opportunity.graph.discovery.js +397 -0
- package/dist/opportunities/application/opportunity.graph.evaluation.d.ts +82 -0
- package/dist/opportunities/application/opportunity.graph.evaluation.js +608 -0
- package/dist/opportunities/application/opportunity.graph.js +120 -3690
- package/dist/opportunities/application/opportunity.graph.modes.d.ts +538 -0
- package/dist/opportunities/application/opportunity.graph.modes.js +538 -0
- package/dist/opportunities/application/opportunity.graph.negotiate.d.ts +109 -0
- package/dist/opportunities/application/opportunity.graph.negotiate.js +392 -0
- package/dist/opportunities/application/opportunity.graph.persist-node.d.ts +93 -0
- package/dist/opportunities/application/opportunity.graph.persist-node.js +765 -0
- package/dist/opportunities/application/opportunity.graph.prep.d.ts +150 -0
- package/dist/opportunities/application/opportunity.graph.prep.js +381 -0
- package/dist/opportunities/application/opportunity.graph.shared.d.ts +173 -0
- package/dist/opportunities/application/opportunity.graph.shared.js +199 -0
- package/dist/opportunities/application/opportunity.presentation.d.ts +399 -0
- package/dist/opportunities/application/{opportunity.presenter.js → opportunity.presentation.js} +735 -9
- package/dist/opportunities/application/opportunity.tools.cards.d.ts +152 -0
- package/dist/opportunities/application/opportunity.tools.cards.js +235 -0
- package/dist/opportunities/application/opportunity.tools.d.ts +8 -114
- package/dist/opportunities/application/opportunity.tools.js +28 -711
- package/dist/opportunities/application/opportunity.tools.list.d.ts +10 -0
- package/dist/opportunities/application/opportunity.tools.list.js +493 -0
- package/dist/opportunities/domain/index.d.ts +3 -3
- package/dist/opportunities/domain/index.js +3 -3
- package/dist/opportunities/domain/opportunity.state.d.ts +19 -19
- package/dist/opportunities/index.d.ts +6 -6
- package/dist/opportunities/index.js +4 -4
- package/dist/opportunities/ports/opportunity.tools.port.d.ts +1 -1
- package/dist/opportunities/radar/radar.graph.d.ts +57 -13
- package/dist/opportunities/radar/radar.graph.js +470 -471
- package/dist/premises/premise.graph.d.ts +88 -20
- package/dist/premises/premise.graph.js +207 -218
- package/dist/questions/domain/question.schema.d.ts +40 -40
- package/dist/shared/agent/tool.helpers.d.ts +21 -0
- package/dist/shared/agent/tool.registry.d.ts +4 -3
- package/dist/shared/agent/tool.registry.js +1 -1
- package/dist/shared/interfaces/database.capabilities.d.ts +100 -0
- package/dist/shared/interfaces/database.capabilities.js +7 -0
- package/dist/shared/interfaces/database.entities.d.ts +533 -0
- package/dist/shared/interfaces/database.entities.js +10 -0
- package/dist/shared/interfaces/database.identity-queries.d.ts +294 -0
- package/dist/shared/interfaces/database.identity-queries.js +4 -0
- package/dist/shared/interfaces/database.interface.d.ts +15 -2277
- package/dist/shared/interfaces/database.interface.js +8 -0
- package/dist/shared/interfaces/database.member-queries.d.ts +228 -0
- package/dist/shared/interfaces/database.member-queries.js +4 -0
- package/dist/shared/interfaces/database.negotiation.d.ts +305 -0
- package/dist/shared/interfaces/database.negotiation.js +26 -0
- package/dist/shared/interfaces/database.network-queries.d.ts +277 -0
- package/dist/shared/interfaces/database.network-queries.js +4 -0
- package/dist/shared/interfaces/database.opportunity-queries.d.ts +285 -0
- package/dist/shared/interfaces/database.opportunity-queries.js +4 -0
- package/dist/shared/interfaces/database.port.d.ts +322 -0
- package/dist/shared/interfaces/database.port.js +29 -0
- package/dist/shared/schemas/discovery-question.schema.d.ts +14 -14
- package/dist/shared/schemas/negotiation-digest.schema.d.ts +2 -2
- package/package.json +2 -15
- package/dist/opportunities/application/opportunity.card-presentation.d.ts +0 -44
- package/dist/opportunities/application/opportunity.card-presentation.js +0 -93
- package/dist/opportunities/application/opportunity.presenter.d.ts +0 -156
- package/dist/opportunities/domain/opportunity.presentation-cache.d.ts +0 -5
- package/dist/opportunities/domain/opportunity.presentation-cache.js +0 -11
- package/dist/opportunities/domain/opportunity.presentation.d.ts +0 -76
- package/dist/opportunities/domain/opportunity.presentation.js +0 -516
- package/dist/opportunities/domain/opportunity.safe-presentation.d.ts +0 -104
- package/dist/opportunities/domain/opportunity.safe-presentation.js +0 -102
|
@@ -75,7 +75,7 @@ export declare const IntentNetworkGraphState: import("@langchain/langgraph").Ann
|
|
|
75
75
|
/** Target intent for assign/read-by-intent. From tool arg. */
|
|
76
76
|
intentId: import("@langchain/langgraph").BaseChannel<string | undefined, string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined, unknown>;
|
|
77
77
|
/** Operation mode. */
|
|
78
|
-
operationMode: import("@langchain/langgraph").BaseChannel<"
|
|
78
|
+
operationMode: import("@langchain/langgraph").BaseChannel<"create" | "delete" | "read", "create" | "delete" | "read" | import("@langchain/langgraph").OverwriteValue<"create" | "delete" | "read">, unknown>;
|
|
79
79
|
/**
|
|
80
80
|
* When true, skip LLM evaluation and assign directly (manual_override).
|
|
81
81
|
* When false, run IntentIndexer evaluation (automatic mode).
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { NetworkMembershipGraphDatabase } from "../ports/index.js";
|
|
2
|
+
import { NetworkMembershipGraphState } from "../domain/index.js";
|
|
2
3
|
/**
|
|
3
4
|
* Factory class to build and compile the Network Membership Graph.
|
|
4
5
|
*
|
|
@@ -28,13 +29,20 @@ import type { NetworkMembershipGraphDatabase } from "../ports/index.js";
|
|
|
28
29
|
*
|
|
29
30
|
* IND-546: canonical home — previously network/membership/membership.graph.ts.
|
|
30
31
|
*/
|
|
32
|
+
/** The graph's channel state, as every node sees it. */
|
|
33
|
+
export type NetworkMembershipState = typeof NetworkMembershipGraphState.State;
|
|
34
|
+
/** Everything the membership nodes reach for. */
|
|
35
|
+
export interface NetworkMembershipGraphDeps {
|
|
36
|
+
database: NetworkMembershipGraphDatabase;
|
|
37
|
+
}
|
|
31
38
|
export declare class NetworkMembershipGraphFactory {
|
|
32
|
-
|
|
39
|
+
/** Resolved dependency bag shared by every node. */
|
|
40
|
+
readonly deps: NetworkMembershipGraphDeps;
|
|
33
41
|
constructor(database: NetworkMembershipGraphDatabase);
|
|
34
42
|
createGraph(): import("@langchain/langgraph").CompiledStateGraph<{
|
|
35
43
|
userId: string;
|
|
36
44
|
networkId: string;
|
|
37
|
-
operationMode: "
|
|
45
|
+
operationMode: "create" | "delete" | "read";
|
|
38
46
|
targetUserId: string | undefined;
|
|
39
47
|
readResult: {
|
|
40
48
|
networkId: string;
|
|
@@ -57,7 +65,7 @@ export declare class NetworkMembershipGraphFactory {
|
|
|
57
65
|
}, {
|
|
58
66
|
userId?: string | undefined;
|
|
59
67
|
networkId?: string | undefined;
|
|
60
|
-
operationMode?: "
|
|
68
|
+
operationMode?: "create" | "delete" | "read" | import("@langchain/langgraph").OverwriteValue<"create" | "delete" | "read"> | undefined;
|
|
61
69
|
targetUserId?: string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined;
|
|
62
70
|
readResult?: {
|
|
63
71
|
networkId: string;
|
|
@@ -103,7 +111,7 @@ export declare class NetworkMembershipGraphFactory {
|
|
|
103
111
|
(): import("@langchain/langgraph").LastValue<string>;
|
|
104
112
|
Root: <S extends import("@langchain/langgraph").StateDefinition>(sd: S) => import("@langchain/langgraph").AnnotationRoot<S>;
|
|
105
113
|
};
|
|
106
|
-
operationMode: import("@langchain/langgraph").BaseChannel<"
|
|
114
|
+
operationMode: import("@langchain/langgraph").BaseChannel<"create" | "delete" | "read", "create" | "delete" | "read" | import("@langchain/langgraph").OverwriteValue<"create" | "delete" | "read">, unknown>;
|
|
107
115
|
targetUserId: import("@langchain/langgraph").BaseChannel<string | undefined, string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined, unknown>;
|
|
108
116
|
readResult: import("@langchain/langgraph").BaseChannel<{
|
|
109
117
|
networkId: string;
|
|
@@ -164,7 +172,7 @@ export declare class NetworkMembershipGraphFactory {
|
|
|
164
172
|
(): import("@langchain/langgraph").LastValue<string>;
|
|
165
173
|
Root: <S extends import("@langchain/langgraph").StateDefinition>(sd: S) => import("@langchain/langgraph").AnnotationRoot<S>;
|
|
166
174
|
};
|
|
167
|
-
operationMode: import("@langchain/langgraph").BaseChannel<"
|
|
175
|
+
operationMode: import("@langchain/langgraph").BaseChannel<"create" | "delete" | "read", "create" | "delete" | "read" | import("@langchain/langgraph").OverwriteValue<"create" | "delete" | "read">, unknown>;
|
|
168
176
|
targetUserId: import("@langchain/langgraph").BaseChannel<string | undefined, string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined, unknown>;
|
|
169
177
|
readResult: import("@langchain/langgraph").BaseChannel<{
|
|
170
178
|
networkId: string;
|
|
@@ -268,3 +276,73 @@ export declare class NetworkMembershipGraphFactory {
|
|
|
268
276
|
};
|
|
269
277
|
}, unknown, unknown, []>;
|
|
270
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Add Member Node: Add a user as a member of a network.
|
|
281
|
+
*
|
|
282
|
+
* Two sub-paths:
|
|
283
|
+
* 1. Self-join (targetUserId === userId): only allowed for 'anyone' join policy.
|
|
284
|
+
* 2. Invite others (targetUserId !== userId): caller must be a member; owner-only
|
|
285
|
+
* for invite_only networks.
|
|
286
|
+
*/
|
|
287
|
+
export declare function addMemberNode(state: NetworkMembershipState, deps: NetworkMembershipGraphDeps): Promise<{
|
|
288
|
+
mutationResult: {
|
|
289
|
+
success: boolean;
|
|
290
|
+
error: string;
|
|
291
|
+
message?: undefined;
|
|
292
|
+
};
|
|
293
|
+
} | {
|
|
294
|
+
mutationResult: {
|
|
295
|
+
success: boolean;
|
|
296
|
+
message: string;
|
|
297
|
+
error?: undefined;
|
|
298
|
+
};
|
|
299
|
+
}>;
|
|
300
|
+
/**
|
|
301
|
+
* List Members Node: List all members of a network.
|
|
302
|
+
* Validates that the caller is a member before returning results.
|
|
303
|
+
*/
|
|
304
|
+
export declare function listMembersNode(state: NetworkMembershipState, deps: NetworkMembershipGraphDeps): Promise<{
|
|
305
|
+
readResult: {
|
|
306
|
+
networkId: string;
|
|
307
|
+
count: number;
|
|
308
|
+
members: never[];
|
|
309
|
+
};
|
|
310
|
+
error: string;
|
|
311
|
+
} | {
|
|
312
|
+
readResult: {
|
|
313
|
+
networkId: string;
|
|
314
|
+
count: number;
|
|
315
|
+
members: {
|
|
316
|
+
userId: string;
|
|
317
|
+
name: string;
|
|
318
|
+
avatar: string | null;
|
|
319
|
+
permissions: string[];
|
|
320
|
+
intentCount: number;
|
|
321
|
+
joinedAt: Date;
|
|
322
|
+
}[];
|
|
323
|
+
};
|
|
324
|
+
error?: undefined;
|
|
325
|
+
} | {
|
|
326
|
+
error: string;
|
|
327
|
+
readResult?: undefined;
|
|
328
|
+
}>;
|
|
329
|
+
/**
|
|
330
|
+
* Remove Member Node: Remove a member from a network (owner only).
|
|
331
|
+
*
|
|
332
|
+
* The owner themselves cannot be removed via this path. To transfer or
|
|
333
|
+
* remove the owner, delete the network or promote another member first.
|
|
334
|
+
*/
|
|
335
|
+
export declare function removeMemberNode(state: NetworkMembershipState, deps: NetworkMembershipGraphDeps): Promise<{
|
|
336
|
+
mutationResult: {
|
|
337
|
+
success: boolean;
|
|
338
|
+
error: string;
|
|
339
|
+
message?: undefined;
|
|
340
|
+
};
|
|
341
|
+
} | {
|
|
342
|
+
mutationResult: {
|
|
343
|
+
success: boolean;
|
|
344
|
+
message: string;
|
|
345
|
+
error?: undefined;
|
|
346
|
+
};
|
|
347
|
+
}>;
|
|
348
|
+
export declare function routeByMode(state: NetworkMembershipState): string;
|
|
@@ -3,219 +3,17 @@ import { protocolLogger } from "../../shared/observability/protocol.logger.js";
|
|
|
3
3
|
import { timed } from "../../shared/observability/performance.js";
|
|
4
4
|
import { NetworkMembershipGraphState } from "../domain/index.js";
|
|
5
5
|
const logger = protocolLogger("NetworkMembershipGraphFactory");
|
|
6
|
-
/**
|
|
7
|
-
* Factory class to build and compile the Network Membership Graph.
|
|
8
|
-
*
|
|
9
|
-
* Handles CRUD operations for the index_members table:
|
|
10
|
-
* - create: Add a member to a network (validates join policy and ownership)
|
|
11
|
-
* - read: List members of a network (validates caller is member)
|
|
12
|
-
* - delete: Remove a member from a network (owner-only)
|
|
13
|
-
*
|
|
14
|
-
* ## Membership authority policy
|
|
15
|
-
*
|
|
16
|
-
* Self-join (`targetUserId === userId`):
|
|
17
|
-
* - Allowed only when `joinPolicy: 'anyone'`.
|
|
18
|
-
* - Idempotent: already-a-member returns success.
|
|
19
|
-
*
|
|
20
|
-
* Invite others (`targetUserId !== userId`):
|
|
21
|
-
* - Caller must be a member of the network.
|
|
22
|
-
* - For `invite_only` networks, caller must also be the owner.
|
|
23
|
-
* - Idempotent: already-a-member returns success.
|
|
24
|
-
*
|
|
25
|
-
* Remove member:
|
|
26
|
-
* - Caller must be the network owner.
|
|
27
|
-
* - Cannot remove the owner (delete the network instead).
|
|
28
|
-
* - Cannot remove yourself via this path (use leave-network flow).
|
|
29
|
-
*
|
|
30
|
-
* Flow:
|
|
31
|
-
* START → routerNode → {addMemberNode | listMembersNode | removeMemberNode} → END
|
|
32
|
-
*
|
|
33
|
-
* IND-546: canonical home — previously network/membership/membership.graph.ts.
|
|
34
|
-
*/
|
|
35
6
|
export class NetworkMembershipGraphFactory {
|
|
36
7
|
constructor(database) {
|
|
37
|
-
this.
|
|
8
|
+
this.deps = { database };
|
|
38
9
|
}
|
|
39
10
|
createGraph() {
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Add Member Node: Add a user as a member of a network.
|
|
43
|
-
*
|
|
44
|
-
* Two sub-paths:
|
|
45
|
-
* 1. Self-join (targetUserId === userId): only allowed for 'anyone' join policy.
|
|
46
|
-
* 2. Invite others (targetUserId !== userId): caller must be a member; owner-only
|
|
47
|
-
* for invite_only networks.
|
|
48
|
-
*/
|
|
49
|
-
const addMemberNode = async (state) => {
|
|
50
|
-
return timed("NetworkMembershipGraph.addMember", async () => {
|
|
51
|
-
logger.verbose("Add member to network", {
|
|
52
|
-
userId: state.userId,
|
|
53
|
-
networkId: state.networkId,
|
|
54
|
-
targetUserId: state.targetUserId,
|
|
55
|
-
});
|
|
56
|
-
if (!state.targetUserId) {
|
|
57
|
-
return { mutationResult: { success: false, error: "targetUserId is required." } };
|
|
58
|
-
}
|
|
59
|
-
try {
|
|
60
|
-
const networkRecord = await this.database.getNetworkWithPermissions(state.networkId);
|
|
61
|
-
if (!networkRecord) {
|
|
62
|
-
return { mutationResult: { success: false, error: "Network not found." } };
|
|
63
|
-
}
|
|
64
|
-
const joinPolicy = networkRecord.permissions.joinPolicy;
|
|
65
|
-
const isSelfJoin = state.targetUserId === state.userId;
|
|
66
|
-
if (isSelfJoin) {
|
|
67
|
-
// Self-join: only allowed for open networks
|
|
68
|
-
if (joinPolicy !== 'anyone') {
|
|
69
|
-
return {
|
|
70
|
-
mutationResult: {
|
|
71
|
-
success: false,
|
|
72
|
-
error: "This network is invite-only. You cannot join without an invitation from an existing member.",
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
const result = await this.database.addMemberToNetwork(state.networkId, state.targetUserId, 'member');
|
|
77
|
-
if (result.alreadyMember) {
|
|
78
|
-
return { mutationResult: { success: true, message: "You are already a member of this network." } };
|
|
79
|
-
}
|
|
80
|
-
return { mutationResult: { success: true, message: `You have joined "${networkRecord.title}".` } };
|
|
81
|
-
}
|
|
82
|
-
// Inviting others: caller must be a member first
|
|
83
|
-
const isMember = await this.database.isNetworkMember(state.networkId, state.userId);
|
|
84
|
-
if (!isMember) {
|
|
85
|
-
return { mutationResult: { success: false, error: "You must be a member of that network to add others." } };
|
|
86
|
-
}
|
|
87
|
-
if (joinPolicy === 'invite_only') {
|
|
88
|
-
const isOwner = await this.database.isIndexOwner(state.networkId, state.userId);
|
|
89
|
-
if (!isOwner) {
|
|
90
|
-
return { mutationResult: { success: false, error: "Only the network owner can add members when the network is invite-only." } };
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
const result = await this.database.addMemberToNetwork(state.networkId, state.targetUserId, 'member');
|
|
94
|
-
if (result.alreadyMember) {
|
|
95
|
-
return { mutationResult: { success: true, message: "That user is already a member of this network." } };
|
|
96
|
-
}
|
|
97
|
-
return { mutationResult: { success: true, message: "Member added to the network." } };
|
|
98
|
-
}
|
|
99
|
-
catch (err) {
|
|
100
|
-
logger.error("Add member failed", { error: err });
|
|
101
|
-
return {
|
|
102
|
-
mutationResult: {
|
|
103
|
-
success: false,
|
|
104
|
-
error: err instanceof Error ? err.message : "Failed to add member.",
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
};
|
|
110
|
-
/**
|
|
111
|
-
* List Members Node: List all members of a network.
|
|
112
|
-
* Validates that the caller is a member before returning results.
|
|
113
|
-
*/
|
|
114
|
-
const listMembersNode = async (state) => {
|
|
115
|
-
return timed("NetworkMembershipGraph.listMembers", async () => {
|
|
116
|
-
logger.verbose("List network members", {
|
|
117
|
-
userId: state.userId,
|
|
118
|
-
networkId: state.networkId,
|
|
119
|
-
});
|
|
120
|
-
try {
|
|
121
|
-
const isMember = await this.database.isNetworkMember(state.networkId, state.userId);
|
|
122
|
-
if (!isMember) {
|
|
123
|
-
return {
|
|
124
|
-
readResult: {
|
|
125
|
-
networkId: state.networkId,
|
|
126
|
-
count: 0,
|
|
127
|
-
members: [],
|
|
128
|
-
},
|
|
129
|
-
error: "Network not found or you are not a member.",
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
const members = await this.database.getNetworkMembersForMember(state.networkId, state.userId);
|
|
133
|
-
return {
|
|
134
|
-
readResult: {
|
|
135
|
-
networkId: state.networkId,
|
|
136
|
-
count: members.length,
|
|
137
|
-
members: members.map((m) => ({
|
|
138
|
-
userId: m.userId,
|
|
139
|
-
name: m.name,
|
|
140
|
-
avatar: m.avatar,
|
|
141
|
-
permissions: m.permissions,
|
|
142
|
-
intentCount: m.intentCount,
|
|
143
|
-
joinedAt: m.joinedAt,
|
|
144
|
-
})),
|
|
145
|
-
},
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
catch (err) {
|
|
149
|
-
logger.error("List members failed", { error: err });
|
|
150
|
-
if (err instanceof Error && err.message === "Access denied: Not a member of this network") {
|
|
151
|
-
return { error: "You must be a member of that network." };
|
|
152
|
-
}
|
|
153
|
-
return { error: "Failed to fetch network members." };
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
};
|
|
157
|
-
/**
|
|
158
|
-
* Remove Member Node: Remove a member from a network (owner only).
|
|
159
|
-
*
|
|
160
|
-
* The owner themselves cannot be removed via this path. To transfer or
|
|
161
|
-
* remove the owner, delete the network or promote another member first.
|
|
162
|
-
*/
|
|
163
|
-
const removeMemberNode = async (state) => {
|
|
164
|
-
return timed("NetworkMembershipGraph.removeMember", async () => {
|
|
165
|
-
logger.verbose("Remove member from network", {
|
|
166
|
-
userId: state.userId,
|
|
167
|
-
networkId: state.networkId,
|
|
168
|
-
targetUserId: state.targetUserId,
|
|
169
|
-
});
|
|
170
|
-
if (!state.targetUserId) {
|
|
171
|
-
return { mutationResult: { success: false, error: "targetUserId is required." } };
|
|
172
|
-
}
|
|
173
|
-
// Cannot remove yourself via this flow
|
|
174
|
-
if (state.targetUserId === state.userId) {
|
|
175
|
-
return { mutationResult: { success: false, error: "You cannot remove yourself. Use 'leave network' instead." } };
|
|
176
|
-
}
|
|
177
|
-
try {
|
|
178
|
-
const isOwner = await this.database.isIndexOwner(state.networkId, state.userId);
|
|
179
|
-
if (!isOwner) {
|
|
180
|
-
return { mutationResult: { success: false, error: "Only the network owner can remove members." } };
|
|
181
|
-
}
|
|
182
|
-
const result = await this.database.removeMemberFromIndex(state.networkId, state.targetUserId);
|
|
183
|
-
if (result.wasOwner) {
|
|
184
|
-
return { mutationResult: { success: false, error: "Cannot remove the network owner." } };
|
|
185
|
-
}
|
|
186
|
-
if (result.notMember) {
|
|
187
|
-
return { mutationResult: { success: false, error: "User is not a member of this network." } };
|
|
188
|
-
}
|
|
189
|
-
if (!result.success) {
|
|
190
|
-
return { mutationResult: { success: false, error: "Failed to remove member." } };
|
|
191
|
-
}
|
|
192
|
-
return {
|
|
193
|
-
mutationResult: {
|
|
194
|
-
success: true,
|
|
195
|
-
message: "Member removed from the network.",
|
|
196
|
-
},
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
catch (err) {
|
|
200
|
-
logger.error("Remove member failed", { error: err });
|
|
201
|
-
return { mutationResult: { success: false, error: "Failed to remove member." } };
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
};
|
|
205
|
-
// --- CONDITIONAL ROUTING ---
|
|
206
|
-
const routeByMode = (state) => {
|
|
207
|
-
switch (state.operationMode) {
|
|
208
|
-
case 'create': return 'add_member';
|
|
209
|
-
case 'read': return 'list_members';
|
|
210
|
-
case 'delete': return 'remove_member';
|
|
211
|
-
default: return 'list_members';
|
|
212
|
-
}
|
|
213
|
-
};
|
|
11
|
+
const deps = this.deps;
|
|
214
12
|
// --- GRAPH ASSEMBLY ---
|
|
215
13
|
const workflow = new StateGraph(NetworkMembershipGraphState)
|
|
216
|
-
.addNode("add_member", addMemberNode)
|
|
217
|
-
.addNode("list_members", listMembersNode)
|
|
218
|
-
.addNode("remove_member", removeMemberNode)
|
|
14
|
+
.addNode("add_member", (state) => addMemberNode(state, deps))
|
|
15
|
+
.addNode("list_members", (state) => listMembersNode(state, deps))
|
|
16
|
+
.addNode("remove_member", (state) => removeMemberNode(state, deps))
|
|
219
17
|
.addConditionalEdges(START, routeByMode, {
|
|
220
18
|
add_member: "add_member",
|
|
221
19
|
list_members: "list_members",
|
|
@@ -227,3 +25,175 @@ export class NetworkMembershipGraphFactory {
|
|
|
227
25
|
return workflow.compile();
|
|
228
26
|
}
|
|
229
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Add Member Node: Add a user as a member of a network.
|
|
30
|
+
*
|
|
31
|
+
* Two sub-paths:
|
|
32
|
+
* 1. Self-join (targetUserId === userId): only allowed for 'anyone' join policy.
|
|
33
|
+
* 2. Invite others (targetUserId !== userId): caller must be a member; owner-only
|
|
34
|
+
* for invite_only networks.
|
|
35
|
+
*/
|
|
36
|
+
export async function addMemberNode(state, deps) {
|
|
37
|
+
return timed("NetworkMembershipGraph.addMember", async () => {
|
|
38
|
+
logger.verbose("Add member to network", {
|
|
39
|
+
userId: state.userId,
|
|
40
|
+
networkId: state.networkId,
|
|
41
|
+
targetUserId: state.targetUserId,
|
|
42
|
+
});
|
|
43
|
+
if (!state.targetUserId) {
|
|
44
|
+
return { mutationResult: { success: false, error: "targetUserId is required." } };
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const networkRecord = await deps.database.getNetworkWithPermissions(state.networkId);
|
|
48
|
+
if (!networkRecord) {
|
|
49
|
+
return { mutationResult: { success: false, error: "Network not found." } };
|
|
50
|
+
}
|
|
51
|
+
const joinPolicy = networkRecord.permissions.joinPolicy;
|
|
52
|
+
const isSelfJoin = state.targetUserId === state.userId;
|
|
53
|
+
if (isSelfJoin) {
|
|
54
|
+
// Self-join: only allowed for open networks
|
|
55
|
+
if (joinPolicy !== 'anyone') {
|
|
56
|
+
return {
|
|
57
|
+
mutationResult: {
|
|
58
|
+
success: false,
|
|
59
|
+
error: "This network is invite-only. You cannot join without an invitation from an existing member.",
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const result = await deps.database.addMemberToNetwork(state.networkId, state.targetUserId, 'member');
|
|
64
|
+
if (result.alreadyMember) {
|
|
65
|
+
return { mutationResult: { success: true, message: "You are already a member of this network." } };
|
|
66
|
+
}
|
|
67
|
+
return { mutationResult: { success: true, message: `You have joined "${networkRecord.title}".` } };
|
|
68
|
+
}
|
|
69
|
+
// Inviting others: caller must be a member first
|
|
70
|
+
const isMember = await deps.database.isNetworkMember(state.networkId, state.userId);
|
|
71
|
+
if (!isMember) {
|
|
72
|
+
return { mutationResult: { success: false, error: "You must be a member of that network to add others." } };
|
|
73
|
+
}
|
|
74
|
+
if (joinPolicy === 'invite_only') {
|
|
75
|
+
const isOwner = await deps.database.isIndexOwner(state.networkId, state.userId);
|
|
76
|
+
if (!isOwner) {
|
|
77
|
+
return { mutationResult: { success: false, error: "Only the network owner can add members when the network is invite-only." } };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const result = await deps.database.addMemberToNetwork(state.networkId, state.targetUserId, 'member');
|
|
81
|
+
if (result.alreadyMember) {
|
|
82
|
+
return { mutationResult: { success: true, message: "That user is already a member of this network." } };
|
|
83
|
+
}
|
|
84
|
+
return { mutationResult: { success: true, message: "Member added to the network." } };
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
logger.error("Add member failed", { error: err });
|
|
88
|
+
return {
|
|
89
|
+
mutationResult: {
|
|
90
|
+
success: false,
|
|
91
|
+
error: err instanceof Error ? err.message : "Failed to add member.",
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* List Members Node: List all members of a network.
|
|
99
|
+
* Validates that the caller is a member before returning results.
|
|
100
|
+
*/
|
|
101
|
+
export async function listMembersNode(state, deps) {
|
|
102
|
+
return timed("NetworkMembershipGraph.listMembers", async () => {
|
|
103
|
+
logger.verbose("List network members", {
|
|
104
|
+
userId: state.userId,
|
|
105
|
+
networkId: state.networkId,
|
|
106
|
+
});
|
|
107
|
+
try {
|
|
108
|
+
const isMember = await deps.database.isNetworkMember(state.networkId, state.userId);
|
|
109
|
+
if (!isMember) {
|
|
110
|
+
return {
|
|
111
|
+
readResult: {
|
|
112
|
+
networkId: state.networkId,
|
|
113
|
+
count: 0,
|
|
114
|
+
members: [],
|
|
115
|
+
},
|
|
116
|
+
error: "Network not found or you are not a member.",
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const members = await deps.database.getNetworkMembersForMember(state.networkId, state.userId);
|
|
120
|
+
return {
|
|
121
|
+
readResult: {
|
|
122
|
+
networkId: state.networkId,
|
|
123
|
+
count: members.length,
|
|
124
|
+
members: members.map((m) => ({
|
|
125
|
+
userId: m.userId,
|
|
126
|
+
name: m.name,
|
|
127
|
+
avatar: m.avatar,
|
|
128
|
+
permissions: m.permissions,
|
|
129
|
+
intentCount: m.intentCount,
|
|
130
|
+
joinedAt: m.joinedAt,
|
|
131
|
+
})),
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
logger.error("List members failed", { error: err });
|
|
137
|
+
if (err instanceof Error && err.message === "Access denied: Not a member of this network") {
|
|
138
|
+
return { error: "You must be a member of that network." };
|
|
139
|
+
}
|
|
140
|
+
return { error: "Failed to fetch network members." };
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Remove Member Node: Remove a member from a network (owner only).
|
|
146
|
+
*
|
|
147
|
+
* The owner themselves cannot be removed via this path. To transfer or
|
|
148
|
+
* remove the owner, delete the network or promote another member first.
|
|
149
|
+
*/
|
|
150
|
+
export async function removeMemberNode(state, deps) {
|
|
151
|
+
return timed("NetworkMembershipGraph.removeMember", async () => {
|
|
152
|
+
logger.verbose("Remove member from network", {
|
|
153
|
+
userId: state.userId,
|
|
154
|
+
networkId: state.networkId,
|
|
155
|
+
targetUserId: state.targetUserId,
|
|
156
|
+
});
|
|
157
|
+
if (!state.targetUserId) {
|
|
158
|
+
return { mutationResult: { success: false, error: "targetUserId is required." } };
|
|
159
|
+
}
|
|
160
|
+
// Cannot remove yourself via this flow
|
|
161
|
+
if (state.targetUserId === state.userId) {
|
|
162
|
+
return { mutationResult: { success: false, error: "You cannot remove yourself. Use 'leave network' instead." } };
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const isOwner = await deps.database.isIndexOwner(state.networkId, state.userId);
|
|
166
|
+
if (!isOwner) {
|
|
167
|
+
return { mutationResult: { success: false, error: "Only the network owner can remove members." } };
|
|
168
|
+
}
|
|
169
|
+
const result = await deps.database.removeMemberFromIndex(state.networkId, state.targetUserId);
|
|
170
|
+
if (result.wasOwner) {
|
|
171
|
+
return { mutationResult: { success: false, error: "Cannot remove the network owner." } };
|
|
172
|
+
}
|
|
173
|
+
if (result.notMember) {
|
|
174
|
+
return { mutationResult: { success: false, error: "User is not a member of this network." } };
|
|
175
|
+
}
|
|
176
|
+
if (!result.success) {
|
|
177
|
+
return { mutationResult: { success: false, error: "Failed to remove member." } };
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
mutationResult: {
|
|
181
|
+
success: true,
|
|
182
|
+
message: "Member removed from the network.",
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
logger.error("Remove member failed", { error: err });
|
|
188
|
+
return { mutationResult: { success: false, error: "Failed to remove member." } };
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
export function routeByMode(state) {
|
|
193
|
+
switch (state.operationMode) {
|
|
194
|
+
case 'create': return 'add_member';
|
|
195
|
+
case 'read': return 'list_members';
|
|
196
|
+
case 'delete': return 'remove_member';
|
|
197
|
+
default: return 'list_members';
|
|
198
|
+
}
|
|
199
|
+
}
|