@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.
Files changed (140) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/IMPLEMENTATION.md +50 -52
  3. package/STABILITY.md +3 -3
  4. package/dist/chat/chat.agent.js +2 -2
  5. package/dist/chat/chat.graph.d.ts +41 -1
  6. package/dist/chat/chat.graph.js +108 -127
  7. package/dist/discovery/hyde.frame.d.ts +4 -4
  8. package/dist/discovery/hyde.graph.d.ts +71 -12
  9. package/dist/discovery/hyde.graph.js +337 -335
  10. package/dist/discovery/lens.inferrer.d.ts +6 -6
  11. package/dist/enrichment/enrichment.graph.d.ts +127 -9
  12. package/dist/enrichment/enrichment.graph.js +633 -655
  13. package/dist/enrichment/enrichment.state.d.ts +1 -1
  14. package/dist/enrichment/enrichment.tools.context-read.d.ts +10 -0
  15. package/dist/enrichment/enrichment.tools.context-read.js +353 -0
  16. package/dist/enrichment/enrichment.tools.context-write.d.ts +9 -0
  17. package/dist/enrichment/enrichment.tools.context-write.js +417 -0
  18. package/dist/enrichment/enrichment.tools.d.ts +11 -1
  19. package/dist/enrichment/enrichment.tools.helpers.d.ts +139 -0
  20. package/dist/enrichment/enrichment.tools.helpers.js +234 -0
  21. package/dist/enrichment/enrichment.tools.js +16 -975
  22. package/dist/index.d.ts +27 -48
  23. package/dist/index.js +17 -27
  24. package/dist/intents/application/intent.graph.d.ts +71 -67
  25. package/dist/intents/application/intent.graph.execute.d.ts +59 -0
  26. package/dist/intents/application/intent.graph.execute.js +301 -0
  27. package/dist/intents/application/intent.graph.infer.d.ts +44 -0
  28. package/dist/intents/application/intent.graph.infer.js +97 -0
  29. package/dist/intents/application/intent.graph.js +96 -888
  30. package/dist/intents/application/intent.graph.reconcile.d.ts +71 -0
  31. package/dist/intents/application/intent.graph.reconcile.js +274 -0
  32. package/dist/intents/application/intent.graph.shared.d.ts +70 -0
  33. package/dist/intents/application/intent.graph.shared.js +153 -0
  34. package/dist/intents/domain/intent.state.d.ts +1 -1
  35. package/dist/maintenance/maintenance.graph.d.ts +66 -3
  36. package/dist/maintenance/maintenance.graph.js +155 -156
  37. package/dist/mcp/mcp.authorization-policy.d.ts +24 -28
  38. package/dist/mcp/mcp.authorization-policy.js +14 -34
  39. package/dist/mcp/mcp.server.d.ts +3 -3
  40. package/dist/mcp/mcp.server.js +12 -16
  41. package/dist/negotiations/application/negotiation.candidates.d.ts +83 -0
  42. package/dist/negotiations/application/negotiation.candidates.js +162 -0
  43. package/dist/negotiations/application/negotiation.graph.d.ts +91 -157
  44. package/dist/negotiations/application/negotiation.graph.finalize.d.ts +5 -0
  45. package/dist/negotiations/application/negotiation.graph.finalize.js +280 -0
  46. package/dist/negotiations/application/negotiation.graph.init.d.ts +54 -0
  47. package/dist/negotiations/application/negotiation.graph.init.js +227 -0
  48. package/dist/negotiations/application/negotiation.graph.js +69 -1388
  49. package/dist/negotiations/application/negotiation.graph.screen.d.ts +23 -0
  50. package/dist/negotiations/application/negotiation.graph.screen.js +108 -0
  51. package/dist/negotiations/application/negotiation.graph.shared.d.ts +75 -0
  52. package/dist/negotiations/application/negotiation.graph.shared.js +125 -0
  53. package/dist/negotiations/application/negotiation.graph.turn.d.ts +105 -0
  54. package/dist/negotiations/application/negotiation.graph.turn.js +484 -0
  55. package/dist/negotiations/domain/negotiation.state.d.ts +1 -1
  56. package/dist/negotiations/domain/negotiation.state.js +0 -1
  57. package/dist/networks/application/indexer.graph.d.ts +165 -7
  58. package/dist/networks/application/indexer.graph.js +339 -388
  59. package/dist/networks/application/indexer.state.d.ts +1 -1
  60. package/dist/networks/application/membership.graph.d.ts +83 -5
  61. package/dist/networks/application/membership.graph.js +177 -207
  62. package/dist/networks/application/network.graph.d.ts +148 -5
  63. package/dist/networks/application/network.graph.js +249 -278
  64. package/dist/networks/domain/membership.state.d.ts +1 -1
  65. package/dist/networks/domain/network.state.d.ts +1 -1
  66. package/dist/opportunities/application/delivery-card.cache.d.ts +1 -1
  67. package/dist/opportunities/application/delivery-card.cache.js +2 -2
  68. package/dist/opportunities/application/index.d.ts +2 -2
  69. package/dist/opportunities/application/index.js +2 -2
  70. package/dist/opportunities/application/opportunity.evaluator.js +1 -1
  71. package/dist/opportunities/application/opportunity.graph.d.ts +686 -473
  72. package/dist/opportunities/application/opportunity.graph.discovery-strategies.d.ts +109 -0
  73. package/dist/opportunities/application/opportunity.graph.discovery-strategies.js +451 -0
  74. package/dist/opportunities/application/opportunity.graph.discovery.d.ts +69 -0
  75. package/dist/opportunities/application/opportunity.graph.discovery.js +397 -0
  76. package/dist/opportunities/application/opportunity.graph.evaluation.d.ts +82 -0
  77. package/dist/opportunities/application/opportunity.graph.evaluation.js +608 -0
  78. package/dist/opportunities/application/opportunity.graph.js +120 -3690
  79. package/dist/opportunities/application/opportunity.graph.modes.d.ts +538 -0
  80. package/dist/opportunities/application/opportunity.graph.modes.js +538 -0
  81. package/dist/opportunities/application/opportunity.graph.negotiate.d.ts +109 -0
  82. package/dist/opportunities/application/opportunity.graph.negotiate.js +392 -0
  83. package/dist/opportunities/application/opportunity.graph.persist-node.d.ts +93 -0
  84. package/dist/opportunities/application/opportunity.graph.persist-node.js +765 -0
  85. package/dist/opportunities/application/opportunity.graph.prep.d.ts +150 -0
  86. package/dist/opportunities/application/opportunity.graph.prep.js +381 -0
  87. package/dist/opportunities/application/opportunity.graph.shared.d.ts +173 -0
  88. package/dist/opportunities/application/opportunity.graph.shared.js +199 -0
  89. package/dist/opportunities/application/opportunity.presentation.d.ts +399 -0
  90. package/dist/opportunities/application/{opportunity.presenter.js → opportunity.presentation.js} +735 -9
  91. package/dist/opportunities/application/opportunity.tools.cards.d.ts +152 -0
  92. package/dist/opportunities/application/opportunity.tools.cards.js +235 -0
  93. package/dist/opportunities/application/opportunity.tools.d.ts +8 -114
  94. package/dist/opportunities/application/opportunity.tools.js +28 -711
  95. package/dist/opportunities/application/opportunity.tools.list.d.ts +10 -0
  96. package/dist/opportunities/application/opportunity.tools.list.js +493 -0
  97. package/dist/opportunities/domain/index.d.ts +3 -3
  98. package/dist/opportunities/domain/index.js +3 -3
  99. package/dist/opportunities/domain/opportunity.state.d.ts +19 -19
  100. package/dist/opportunities/index.d.ts +6 -6
  101. package/dist/opportunities/index.js +4 -4
  102. package/dist/opportunities/ports/opportunity.tools.port.d.ts +1 -1
  103. package/dist/opportunities/radar/radar.graph.d.ts +57 -13
  104. package/dist/opportunities/radar/radar.graph.js +470 -471
  105. package/dist/premises/premise.graph.d.ts +88 -20
  106. package/dist/premises/premise.graph.js +207 -218
  107. package/dist/questions/domain/question.schema.d.ts +40 -40
  108. package/dist/shared/agent/tool.helpers.d.ts +21 -0
  109. package/dist/shared/agent/tool.registry.d.ts +4 -3
  110. package/dist/shared/agent/tool.registry.js +1 -1
  111. package/dist/shared/interfaces/database.capabilities.d.ts +100 -0
  112. package/dist/shared/interfaces/database.capabilities.js +7 -0
  113. package/dist/shared/interfaces/database.entities.d.ts +533 -0
  114. package/dist/shared/interfaces/database.entities.js +10 -0
  115. package/dist/shared/interfaces/database.identity-queries.d.ts +294 -0
  116. package/dist/shared/interfaces/database.identity-queries.js +4 -0
  117. package/dist/shared/interfaces/database.interface.d.ts +15 -2277
  118. package/dist/shared/interfaces/database.interface.js +8 -0
  119. package/dist/shared/interfaces/database.member-queries.d.ts +228 -0
  120. package/dist/shared/interfaces/database.member-queries.js +4 -0
  121. package/dist/shared/interfaces/database.negotiation.d.ts +305 -0
  122. package/dist/shared/interfaces/database.negotiation.js +26 -0
  123. package/dist/shared/interfaces/database.network-queries.d.ts +277 -0
  124. package/dist/shared/interfaces/database.network-queries.js +4 -0
  125. package/dist/shared/interfaces/database.opportunity-queries.d.ts +285 -0
  126. package/dist/shared/interfaces/database.opportunity-queries.js +4 -0
  127. package/dist/shared/interfaces/database.port.d.ts +322 -0
  128. package/dist/shared/interfaces/database.port.js +29 -0
  129. package/dist/shared/schemas/discovery-question.schema.d.ts +14 -14
  130. package/dist/shared/schemas/negotiation-digest.schema.d.ts +2 -2
  131. package/package.json +2 -15
  132. package/dist/opportunities/application/opportunity.card-presentation.d.ts +0 -44
  133. package/dist/opportunities/application/opportunity.card-presentation.js +0 -93
  134. package/dist/opportunities/application/opportunity.presenter.d.ts +0 -156
  135. package/dist/opportunities/domain/opportunity.presentation-cache.d.ts +0 -5
  136. package/dist/opportunities/domain/opportunity.presentation-cache.js +0 -11
  137. package/dist/opportunities/domain/opportunity.presentation.d.ts +0 -76
  138. package/dist/opportunities/domain/opportunity.presentation.js +0 -516
  139. package/dist/opportunities/domain/opportunity.safe-presentation.d.ts +0 -104
  140. package/dist/opportunities/domain/opportunity.safe-presentation.js +0 -102
@@ -3,301 +3,272 @@ import { protocolLogger } from "../../shared/observability/protocol.logger.js";
3
3
  import { timed } from "../../shared/observability/performance.js";
4
4
  import { NetworkGraphState } from "../domain/index.js";
5
5
  const logger = protocolLogger("NetworkGraphFactory");
6
- /**
7
- * Factory class to build and compile the Network (community) lifecycle graph.
8
- *
9
- * Handles create, read, update, and delete operations for networks.
10
- * Membership and intent–network assignment operations are delegated to
11
- * NetworkMembershipGraphFactory and IntentNetworkGraphFactory respectively.
12
- *
13
- * ## Ownership policy
14
- *
15
- * - `create`: the calling user becomes the sole owner. The owner–membership
16
- * write is performed atomically after the network row is created; on failure
17
- * the network is soft-deleted (rollback). Rollback failures are logged but
18
- * do NOT mutate the error response — the caller always receives the first
19
- * meaningful failure, not a noisy rollback message.
20
- * - `update`: owner-only. Any member field may be updated in one call.
21
- * - `delete`: owner-only; the network must have no other members.
22
- *
23
- * ## Scope semantics (read mode)
24
- *
25
- * When `networkId` is set and `showAll` is false, only the focused network and
26
- * the user's personal network (contacts) are returned. This is the strict-scope
27
- * invariant for network-bound agents. Set `showAll: true` to bypass.
28
- *
29
- * Flow:
30
- * START → routerNode → {createNode | readNode | updateNode | deleteNode} → END
31
- *
32
- * IND-546: canonical home — previously network/network.graph.ts.
33
- */
34
6
  export class NetworkGraphFactory {
35
7
  constructor(database) {
36
- this.database = database;
8
+ this.deps = { database };
37
9
  }
38
10
  createGraph() {
39
- // --- NODE DEFINITIONS ---
40
- /**
41
- * Read Node: List networks the user belongs to and owns.
42
- */
43
- const readNode = async (state) => {
44
- return timed("NetworkGraph.read", async () => {
45
- logger.verbose("Read networks", { userId: state.userId, networkId: state.networkId, showAll: state.showAll });
46
- // Shared projections for both the scoped and unscoped read paths.
47
- const projectMembership = (m) => ({
48
- networkId: m.networkId,
49
- title: m.networkTitle,
50
- prompt: m.indexPrompt,
51
- autoAssign: m.autoAssign,
52
- isPersonal: m.isPersonal,
53
- joinedAt: m.joinedAt,
54
- });
55
- const projectOwned = (o) => ({
56
- networkId: o.id,
57
- title: o.title,
58
- prompt: o.prompt,
59
- memberCount: o.memberCount,
60
- intentCount: o.intentCount,
61
- joinPolicy: o.permissions.joinPolicy,
62
- });
63
- try {
64
- const [allMemberships, ownedIndexes, publicIndexesResult] = await Promise.all([
65
- this.database.getNetworkMemberships(state.userId),
66
- this.database.getOwnedIndexes(state.userId),
67
- this.database.getPublicIndexesNotJoined(state.userId),
68
- ]);
69
- // If network-scoped and not showAll, return just that network plus the
70
- // user's personal network (their contacts). The personal network is part
71
- // of every allowed-network reach calculation for network-bound agents,
72
- // and a user in a community-scoped chat still owns their contact list —
73
- // so surfacing it here keeps contact tools discoverable. Other community
74
- // memberships are still hidden, and `publicNetworks` is omitted.
75
- const scopeToCurrentNetwork = state.networkId && !state.showAll;
76
- if (scopeToCurrentNetwork) {
77
- const networkId = state.networkId;
78
- const isMember = await this.database.isNetworkMember(networkId, state.userId);
79
- if (!isMember) {
80
- return {
81
- readResult: {
82
- memberOf: [],
83
- owns: [],
84
- stats: { memberOfCount: 0, ownsCount: 0, scopeNote: "Network not found or you are not a member." },
85
- },
86
- };
87
- }
88
- const scopedMembership = allMemberships.find((m) => m.networkId === networkId);
89
- const personalMembership = scopedMembership?.isPersonal
90
- ? undefined
91
- : allMemberships.find((m) => m.isPersonal);
92
- const scopedOwned = ownedIndexes.find((o) => o.id === networkId);
93
- const personalOwned = personalMembership
94
- ? ownedIndexes.find((o) => o.id === personalMembership.networkId)
95
- : undefined;
96
- const memberOf = [
97
- ...(scopedMembership ? [projectMembership(scopedMembership)] : []),
98
- ...(personalMembership ? [projectMembership(personalMembership)] : []),
99
- ];
100
- const owns = [
101
- ...(scopedOwned ? [projectOwned(scopedOwned)] : []),
102
- ...(personalOwned ? [projectOwned(personalOwned)] : []),
103
- ];
104
- return {
105
- readResult: {
106
- memberOf,
107
- owns,
108
- stats: {
109
- memberOfCount: memberOf.length,
110
- ownsCount: owns.length,
111
- scopeNote: "Showing current network and your personal network. Use showAll: true for all networks.",
112
- },
113
- },
114
- };
115
- }
116
- // Include public networks available to join
117
- const publicNetworks = publicIndexesResult.networks.map((idx) => ({
118
- networkId: idx.id,
119
- title: idx.title,
120
- prompt: idx.prompt,
121
- memberCount: idx.memberCount,
122
- owner: idx.owner,
123
- }));
11
+ const deps = this.deps;
12
+ // --- GRAPH ASSEMBLY ---
13
+ const workflow = new StateGraph(NetworkGraphState)
14
+ .addNode("read", (state) => readNode(state, deps))
15
+ .addNode("create", (state) => createNode(state, deps))
16
+ .addNode("update", (state) => updateNode(state, deps))
17
+ .addNode("delete_idx", (state) => deleteNode(state, deps))
18
+ .addConditionalEdges(START, routeByMode, {
19
+ read: "read",
20
+ create: "create",
21
+ update: "update",
22
+ delete_idx: "delete_idx",
23
+ })
24
+ .addEdge("read", END)
25
+ .addEdge("create", END)
26
+ .addEdge("update", END)
27
+ .addEdge("delete_idx", END);
28
+ return workflow.compile();
29
+ }
30
+ }
31
+ /**
32
+ * Read Node: List networks the user belongs to and owns.
33
+ */
34
+ export async function readNode(state, deps) {
35
+ return timed("NetworkGraph.read", async () => {
36
+ logger.verbose("Read networks", { userId: state.userId, networkId: state.networkId, showAll: state.showAll });
37
+ // Shared projections for both the scoped and unscoped read paths.
38
+ const projectMembership = (m) => ({
39
+ networkId: m.networkId,
40
+ title: m.networkTitle,
41
+ prompt: m.indexPrompt,
42
+ autoAssign: m.autoAssign,
43
+ isPersonal: m.isPersonal,
44
+ joinedAt: m.joinedAt,
45
+ });
46
+ const projectOwned = (o) => ({
47
+ networkId: o.id,
48
+ title: o.title,
49
+ prompt: o.prompt,
50
+ memberCount: o.memberCount,
51
+ intentCount: o.intentCount,
52
+ joinPolicy: o.permissions.joinPolicy,
53
+ });
54
+ try {
55
+ const [allMemberships, ownedIndexes, publicIndexesResult] = await Promise.all([
56
+ deps.database.getNetworkMemberships(state.userId),
57
+ deps.database.getOwnedIndexes(state.userId),
58
+ deps.database.getPublicIndexesNotJoined(state.userId),
59
+ ]);
60
+ // If network-scoped and not showAll, return just that network plus the
61
+ // user's personal network (their contacts). The personal network is part
62
+ // of every allowed-network reach calculation for network-bound agents,
63
+ // and a user in a community-scoped chat still owns their contact list —
64
+ // so surfacing it here keeps contact tools discoverable. Other community
65
+ // memberships are still hidden, and `publicNetworks` is omitted.
66
+ const scopeToCurrentNetwork = state.networkId && !state.showAll;
67
+ if (scopeToCurrentNetwork) {
68
+ const networkId = state.networkId;
69
+ const isMember = await deps.database.isNetworkMember(networkId, state.userId);
70
+ if (!isMember) {
124
71
  return {
125
72
  readResult: {
126
- memberOf: allMemberships.map(projectMembership),
127
- owns: ownedIndexes.map(projectOwned),
128
- publicNetworks,
129
- stats: { memberOfCount: allMemberships.length, ownsCount: ownedIndexes.length, publicNetworksCount: publicNetworks.length },
73
+ memberOf: [],
74
+ owns: [],
75
+ stats: { memberOfCount: 0, ownsCount: 0, scopeNote: "Network not found or you are not a member." },
130
76
  },
131
77
  };
132
78
  }
133
- catch (err) {
134
- logger.error("Read networks failed", { error: err });
135
- return { error: "Failed to fetch network information." };
136
- }
137
- });
138
- };
139
- /**
140
- * Create Node: Create a new network and add user as owner.
141
- *
142
- * Atomicity contract:
143
- * 1. Create the network row.
144
- * 2. Add the calling user as owner.
145
- * 3. On step-2 failure: soft-delete the network (rollback).
146
- * 4. Rollback failures are logged at error level but never override the
147
- * primary failure message returned to the caller.
148
- */
149
- const createNode = async (state) => {
150
- return timed("NetworkGraph.create", async () => {
151
- logger.verbose("Create network", { userId: state.userId, createInput: state.createInput });
152
- if (!state.createInput?.title?.trim()) {
153
- return { mutationResult: { success: false, error: "Title is required." } };
154
- }
155
- let createdNetworkId;
156
- try {
157
- const network = await this.database.createNetwork({
158
- title: state.createInput.title.trim(),
159
- prompt: state.createInput.prompt?.trim() || undefined,
160
- imageUrl: state.createInput.imageUrl ?? undefined,
161
- joinPolicy: state.createInput.joinPolicy,
162
- });
163
- createdNetworkId = network.id;
164
- const added = await this.database.addMemberToNetwork(network.id, state.userId, 'owner');
165
- if (!added.success) {
166
- logger.error("addMemberToNetwork failed; cleaning up orphaned network", { networkId: network.id });
167
- try {
168
- await this.database.softDeleteNetwork(network.id);
169
- }
170
- catch (rollbackError) {
171
- logger.error("Network create rollback failed", {
172
- networkId: network.id,
173
- rollbackFor: "owner_membership",
174
- rollbackErrorKind: rollbackError instanceof Error ? "error" : "non_error",
175
- });
176
- }
177
- return { mutationResult: { success: false, error: "Failed to set you as owner. Network was not created." } };
178
- }
179
- return {
180
- mutationResult: {
181
- success: true,
182
- networkId: network.id,
183
- title: network.title,
184
- message: `Network "${network.title}" created. You are the owner.`,
185
- },
186
- };
187
- }
188
- catch (err) {
189
- logger.error("Create network failed", { error: err });
190
- if (createdNetworkId) {
191
- try {
192
- await this.database.softDeleteNetwork(createdNetworkId);
193
- }
194
- catch (rollbackError) {
195
- logger.error("Network create rollback failed", {
196
- networkId: createdNetworkId,
197
- rollbackFor: "create_operation",
198
- rollbackErrorKind: rollbackError instanceof Error ? "error" : "non_error",
199
- });
200
- }
201
- }
202
- return {
203
- mutationResult: {
204
- success: false,
205
- error: err instanceof Error ? err.message : "Failed to create network.",
79
+ const scopedMembership = allMemberships.find((m) => m.networkId === networkId);
80
+ const personalMembership = scopedMembership?.isPersonal
81
+ ? undefined
82
+ : allMemberships.find((m) => m.isPersonal);
83
+ const scopedOwned = ownedIndexes.find((o) => o.id === networkId);
84
+ const personalOwned = personalMembership
85
+ ? ownedIndexes.find((o) => o.id === personalMembership.networkId)
86
+ : undefined;
87
+ const memberOf = [
88
+ ...(scopedMembership ? [projectMembership(scopedMembership)] : []),
89
+ ...(personalMembership ? [projectMembership(personalMembership)] : []),
90
+ ];
91
+ const owns = [
92
+ ...(scopedOwned ? [projectOwned(scopedOwned)] : []),
93
+ ...(personalOwned ? [projectOwned(personalOwned)] : []),
94
+ ];
95
+ return {
96
+ readResult: {
97
+ memberOf,
98
+ owns,
99
+ stats: {
100
+ memberOfCount: memberOf.length,
101
+ ownsCount: owns.length,
102
+ scopeNote: "Showing current network and your personal network. Use showAll: true for all networks.",
206
103
  },
207
- };
208
- }
104
+ },
105
+ };
106
+ }
107
+ // Include public networks available to join
108
+ const publicNetworks = publicIndexesResult.networks.map((idx) => ({
109
+ networkId: idx.id,
110
+ title: idx.title,
111
+ prompt: idx.prompt,
112
+ memberCount: idx.memberCount,
113
+ owner: idx.owner,
114
+ }));
115
+ return {
116
+ readResult: {
117
+ memberOf: allMemberships.map(projectMembership),
118
+ owns: ownedIndexes.map(projectOwned),
119
+ publicNetworks,
120
+ stats: { memberOfCount: allMemberships.length, ownsCount: ownedIndexes.length, publicNetworksCount: publicNetworks.length },
121
+ },
122
+ };
123
+ }
124
+ catch (err) {
125
+ logger.error("Read networks failed", { error: err });
126
+ return { error: "Failed to fetch network information." };
127
+ }
128
+ });
129
+ }
130
+ /**
131
+ * Create Node: Create a new network and add user as owner.
132
+ *
133
+ * Atomicity contract:
134
+ * 1. Create the network row.
135
+ * 2. Add the calling user as owner.
136
+ * 3. On step-2 failure: soft-delete the network (rollback).
137
+ * 4. Rollback failures are logged at error level but never override the
138
+ * primary failure message returned to the caller.
139
+ */
140
+ export async function createNode(state, deps) {
141
+ return timed("NetworkGraph.create", async () => {
142
+ logger.verbose("Create network", { userId: state.userId, createInput: state.createInput });
143
+ if (!state.createInput?.title?.trim()) {
144
+ return { mutationResult: { success: false, error: "Title is required." } };
145
+ }
146
+ let createdNetworkId;
147
+ try {
148
+ const network = await deps.database.createNetwork({
149
+ title: state.createInput.title.trim(),
150
+ prompt: state.createInput.prompt?.trim() || undefined,
151
+ imageUrl: state.createInput.imageUrl ?? undefined,
152
+ joinPolicy: state.createInput.joinPolicy,
209
153
  });
210
- };
211
- /**
212
- * Update Node: Update network settings (owner only).
213
- */
214
- const updateNode = async (state) => {
215
- return timed("NetworkGraph.update", async () => {
216
- const networkId = state.networkId;
217
- logger.verbose("Update network", { userId: state.userId, networkId, updateInput: state.updateInput });
218
- if (!networkId) {
219
- return { mutationResult: { success: false, error: "networkId is required for update." } };
220
- }
154
+ createdNetworkId = network.id;
155
+ const added = await deps.database.addMemberToNetwork(network.id, state.userId, 'owner');
156
+ if (!added.success) {
157
+ logger.error("addMemberToNetwork failed; cleaning up orphaned network", { networkId: network.id });
221
158
  try {
222
- const isOwner = await this.database.isIndexOwner(networkId, state.userId);
223
- if (!isOwner) {
224
- return { mutationResult: { success: false, error: "You can only modify networks you own." } };
225
- }
226
- await this.database.updateIndexSettings(networkId, state.userId, state.updateInput ?? {});
227
- return {
228
- mutationResult: {
229
- success: true,
230
- networkId,
231
- message: "Network settings updated.",
232
- },
233
- };
234
- }
235
- catch (err) {
236
- logger.error("Update network failed", { error: err });
237
- return { mutationResult: { success: false, error: "Failed to update network." } };
159
+ await deps.database.softDeleteNetwork(network.id);
238
160
  }
239
- });
240
- };
241
- /**
242
- * Delete Node: Soft-delete a network (owner only, sole member).
243
- */
244
- const deleteNode = async (state) => {
245
- return timed("NetworkGraph.delete", async () => {
246
- const networkId = state.networkId;
247
- logger.verbose("Delete network", { userId: state.userId, networkId });
248
- if (!networkId) {
249
- return { mutationResult: { success: false, error: "networkId is required for delete." } };
161
+ catch (rollbackError) {
162
+ logger.error("Network create rollback failed", {
163
+ networkId: network.id,
164
+ rollbackFor: "owner_membership",
165
+ rollbackErrorKind: rollbackError instanceof Error ? "error" : "non_error",
166
+ });
250
167
  }
168
+ return { mutationResult: { success: false, error: "Failed to set you as owner. Network was not created." } };
169
+ }
170
+ return {
171
+ mutationResult: {
172
+ success: true,
173
+ networkId: network.id,
174
+ title: network.title,
175
+ message: `Network "${network.title}" created. You are the owner.`,
176
+ },
177
+ };
178
+ }
179
+ catch (err) {
180
+ logger.error("Create network failed", { error: err });
181
+ if (createdNetworkId) {
251
182
  try {
252
- const isOwner = await this.database.isIndexOwner(networkId, state.userId);
253
- if (!isOwner) {
254
- return { mutationResult: { success: false, error: "You can only delete networks you own." } };
255
- }
256
- const count = await this.database.getNetworkMemberCount(networkId);
257
- if (count > 1) {
258
- return { mutationResult: { success: false, error: "Cannot delete network with other members. Remove members first." } };
259
- }
260
- await this.database.softDeleteNetwork(networkId);
261
- return {
262
- mutationResult: {
263
- success: true,
264
- networkId,
265
- message: "Network deleted.",
266
- },
267
- };
183
+ await deps.database.softDeleteNetwork(createdNetworkId);
268
184
  }
269
- catch (err) {
270
- logger.error("Delete network failed", { error: err });
271
- return { mutationResult: { success: false, error: "Failed to delete network." } };
185
+ catch (rollbackError) {
186
+ logger.error("Network create rollback failed", {
187
+ networkId: createdNetworkId,
188
+ rollbackFor: "create_operation",
189
+ rollbackErrorKind: rollbackError instanceof Error ? "error" : "non_error",
190
+ });
272
191
  }
273
- });
274
- };
275
- // --- CONDITIONAL ROUTING ---
276
- const routeByMode = (state) => {
277
- switch (state.operationMode) {
278
- case 'create': return 'create';
279
- case 'read': return 'read';
280
- case 'update': return 'update';
281
- case 'delete': return 'delete_idx';
282
- default: return 'read';
283
192
  }
284
- };
285
- // --- GRAPH ASSEMBLY ---
286
- const workflow = new StateGraph(NetworkGraphState)
287
- .addNode("read", readNode)
288
- .addNode("create", createNode)
289
- .addNode("update", updateNode)
290
- .addNode("delete_idx", deleteNode)
291
- .addConditionalEdges(START, routeByMode, {
292
- read: "read",
293
- create: "create",
294
- update: "update",
295
- delete_idx: "delete_idx",
296
- })
297
- .addEdge("read", END)
298
- .addEdge("create", END)
299
- .addEdge("update", END)
300
- .addEdge("delete_idx", END);
301
- return workflow.compile();
193
+ return {
194
+ mutationResult: {
195
+ success: false,
196
+ error: err instanceof Error ? err.message : "Failed to create network.",
197
+ },
198
+ };
199
+ }
200
+ });
201
+ }
202
+ /**
203
+ * Update Node: Update network settings (owner only).
204
+ */
205
+ export async function updateNode(state, deps) {
206
+ return timed("NetworkGraph.update", async () => {
207
+ const networkId = state.networkId;
208
+ logger.verbose("Update network", { userId: state.userId, networkId, updateInput: state.updateInput });
209
+ if (!networkId) {
210
+ return { mutationResult: { success: false, error: "networkId is required for update." } };
211
+ }
212
+ try {
213
+ const isOwner = await deps.database.isIndexOwner(networkId, state.userId);
214
+ if (!isOwner) {
215
+ return { mutationResult: { success: false, error: "You can only modify networks you own." } };
216
+ }
217
+ await deps.database.updateIndexSettings(networkId, state.userId, state.updateInput ?? {});
218
+ return {
219
+ mutationResult: {
220
+ success: true,
221
+ networkId,
222
+ message: "Network settings updated.",
223
+ },
224
+ };
225
+ }
226
+ catch (err) {
227
+ logger.error("Update network failed", { error: err });
228
+ return { mutationResult: { success: false, error: "Failed to update network." } };
229
+ }
230
+ });
231
+ }
232
+ /**
233
+ * Delete Node: Soft-delete a network (owner only, sole member).
234
+ */
235
+ export async function deleteNode(state, deps) {
236
+ return timed("NetworkGraph.delete", async () => {
237
+ const networkId = state.networkId;
238
+ logger.verbose("Delete network", { userId: state.userId, networkId });
239
+ if (!networkId) {
240
+ return { mutationResult: { success: false, error: "networkId is required for delete." } };
241
+ }
242
+ try {
243
+ const isOwner = await deps.database.isIndexOwner(networkId, state.userId);
244
+ if (!isOwner) {
245
+ return { mutationResult: { success: false, error: "You can only delete networks you own." } };
246
+ }
247
+ const count = await deps.database.getNetworkMemberCount(networkId);
248
+ if (count > 1) {
249
+ return { mutationResult: { success: false, error: "Cannot delete network with other members. Remove members first." } };
250
+ }
251
+ await deps.database.softDeleteNetwork(networkId);
252
+ return {
253
+ mutationResult: {
254
+ success: true,
255
+ networkId,
256
+ message: "Network deleted.",
257
+ },
258
+ };
259
+ }
260
+ catch (err) {
261
+ logger.error("Delete network failed", { error: err });
262
+ return { mutationResult: { success: false, error: "Failed to delete network." } };
263
+ }
264
+ });
265
+ }
266
+ export function routeByMode(state) {
267
+ switch (state.operationMode) {
268
+ case 'create': return 'create';
269
+ case 'read': return 'read';
270
+ case 'update': return 'update';
271
+ case 'delete': return 'delete_idx';
272
+ default: return 'read';
302
273
  }
303
274
  }
@@ -30,7 +30,7 @@ export declare const NetworkMembershipGraphState: import("@langchain/langgraph")
30
30
  Root: <S extends import("@langchain/langgraph").StateDefinition>(sd: S) => import("@langchain/langgraph").AnnotationRoot<S>;
31
31
  };
32
32
  /** Operation mode. */
33
- operationMode: import("@langchain/langgraph").BaseChannel<"delete" | "create" | "read", "delete" | "create" | "read" | import("@langchain/langgraph").OverwriteValue<"delete" | "create" | "read">, unknown>;
33
+ operationMode: import("@langchain/langgraph").BaseChannel<"create" | "delete" | "read", "create" | "delete" | "read" | import("@langchain/langgraph").OverwriteValue<"create" | "delete" | "read">, unknown>;
34
34
  /** For create/delete: the user being added/removed. */
35
35
  targetUserId: import("@langchain/langgraph").BaseChannel<string | undefined, string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined, unknown>;
36
36
  /** Output for read mode: list of members. */
@@ -23,7 +23,7 @@ export declare const NetworkGraphState: import("@langchain/langgraph").Annotatio
23
23
  /** Target network ID. Required for read/update/delete. From ChatGraph or tool arg. */
24
24
  networkId: import("@langchain/langgraph").BaseChannel<string | undefined, string | import("@langchain/langgraph").OverwriteValue<string | undefined> | undefined, unknown>;
25
25
  /** Operation mode. */
26
- operationMode: import("@langchain/langgraph").BaseChannel<"delete" | "update" | "create" | "read", "delete" | "update" | "create" | "read" | import("@langchain/langgraph").OverwriteValue<"delete" | "update" | "create" | "read">, unknown>;
26
+ operationMode: import("@langchain/langgraph").BaseChannel<"update" | "create" | "delete" | "read", "update" | "create" | "delete" | "read" | import("@langchain/langgraph").OverwriteValue<"update" | "create" | "delete" | "read">, unknown>;
27
27
  /** For create mode: network creation data. */
28
28
  createInput: import("@langchain/langgraph").BaseChannel<{
29
29
  title: string;
@@ -1,5 +1,5 @@
1
1
  import type { Cache } from '../../shared/interfaces/cache.interface.js';
2
- import type { OpportunityPresenter, PresenterDatabase } from './opportunity.presenter.js';
2
+ import type { OpportunityPresenter, PresenterDatabase } from './opportunity.presentation.js';
3
3
  export interface CachedDeliveryCard {
4
4
  opportunityId: string;
5
5
  headline: string;
@@ -1,5 +1,5 @@
1
- import { gatherPresenterContext } from './opportunity.presenter.js';
2
- import { buildDeliveryCardPresentationCacheKey } from '../domain/opportunity.presentation-cache.js';
1
+ import { gatherPresenterContext } from './opportunity.presentation.js';
2
+ import { buildDeliveryCardPresentationCacheKey } from './opportunity.presentation.js';
3
3
  export const DELIVERY_CARD_CACHE_TTL = 24 * 60 * 60; // 24 hours
4
4
  export async function getOrCreateDeliveryCardBatch(cache, presenter, presenterDb, opportunities, viewerId, options) {
5
5
  if (opportunities.length === 0) {
@@ -38,11 +38,11 @@ export * from "./opportunity.persistence-admission.js";
38
38
  export * from "./opportunity.update-admission.js";
39
39
  export * from "./opportunity.newborn-stamping.js";
40
40
  export * from "./opportunity.evaluator.js";
41
- export * from "./opportunity.presenter.js";
41
+ export * from "./opportunity.presentation.js";
42
42
  export * from "./opportunity.introducer.js";
43
43
  export * from "./opportunity.existing-negotiation.js";
44
44
  export * from "./opportunity.graph.js";
45
- export * from "./opportunity.card-presentation.js";
45
+ export * from "./opportunity.presentation.js";
46
46
  export * from "./opportunity.discovery-negotiation-summary.js";
47
47
  export * from "./negotiation-context.loader.js";
48
48
  export * from "./opportunity.feed-selection.js";
@@ -39,11 +39,11 @@ export * from "./opportunity.persistence-admission.js";
39
39
  export * from "./opportunity.update-admission.js";
40
40
  export * from "./opportunity.newborn-stamping.js";
41
41
  export * from "./opportunity.evaluator.js";
42
- export * from "./opportunity.presenter.js";
42
+ export * from "./opportunity.presentation.js";
43
43
  export * from "./opportunity.introducer.js";
44
44
  export * from "./opportunity.existing-negotiation.js";
45
45
  export * from "./opportunity.graph.js";
46
- export * from "./opportunity.card-presentation.js";
46
+ export * from "./opportunity.presentation.js";
47
47
  export * from "./opportunity.discovery-negotiation-summary.js";
48
48
  export * from "./negotiation-context.loader.js";
49
49
  export * from "./opportunity.feed-selection.js";
@@ -12,7 +12,7 @@ import { tool } from "@langchain/core/tools";
12
12
  import { z } from "zod";
13
13
  import { protocolLogger } from "../../shared/observability/protocol.logger.js";
14
14
  import { Timed } from "../../shared/observability/performance.js";
15
- import { stripUuids } from "../domain/opportunity.presentation.js";
15
+ import { stripUuids } from "./opportunity.presentation.js";
16
16
  import { createStructuredModel } from "../../shared/agent/model.config.js";
17
17
  import { invokeWithAbortSignal } from "../../shared/agent/model-signal.js";
18
18
  import { renderOpportunityEvidenceForPrompt } from '../domain/opportunity.evidence.js';