society-protocol 1.0.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/LICENSE +21 -0
- package/README.md +111 -0
- package/dist/adapters.d.ts +101 -0
- package/dist/adapters.d.ts.map +1 -0
- package/dist/adapters.js +764 -0
- package/dist/adapters.js.map +1 -0
- package/dist/agents-md.d.ts +59 -0
- package/dist/agents-md.d.ts.map +1 -0
- package/dist/agents-md.js +204 -0
- package/dist/agents-md.js.map +1 -0
- package/dist/autoconfig.d.ts +137 -0
- package/dist/autoconfig.d.ts.map +1 -0
- package/dist/autoconfig.js +452 -0
- package/dist/autoconfig.js.map +1 -0
- package/dist/bootstrap.d.ts +68 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +304 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/bridges/a2a-bridge.d.ts +156 -0
- package/dist/bridges/a2a-bridge.d.ts.map +1 -0
- package/dist/bridges/a2a-bridge.js +337 -0
- package/dist/bridges/a2a-bridge.js.map +1 -0
- package/dist/bridges/mcp-bridge.d.ts +87 -0
- package/dist/bridges/mcp-bridge.d.ts.map +1 -0
- package/dist/bridges/mcp-bridge.js +332 -0
- package/dist/bridges/mcp-bridge.js.map +1 -0
- package/dist/cache.d.ts +130 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +257 -0
- package/dist/cache.js.map +1 -0
- package/dist/capsules.d.ts +23 -0
- package/dist/capsules.d.ts.map +1 -0
- package/dist/capsules.js +75 -0
- package/dist/capsules.js.map +1 -0
- package/dist/cli/commands.d.ts +8 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +263 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/coc.d.ts +121 -0
- package/dist/coc.d.ts.map +1 -0
- package/dist/coc.js +629 -0
- package/dist/coc.js.map +1 -0
- package/dist/coc.test.d.ts +2 -0
- package/dist/coc.test.d.ts.map +1 -0
- package/dist/coc.test.js +80 -0
- package/dist/coc.test.js.map +1 -0
- package/dist/compression.d.ts +125 -0
- package/dist/compression.d.ts.map +1 -0
- package/dist/compression.js +573 -0
- package/dist/compression.js.map +1 -0
- package/dist/cot-stream.d.ts +220 -0
- package/dist/cot-stream.d.ts.map +1 -0
- package/dist/cot-stream.js +673 -0
- package/dist/cot-stream.js.map +1 -0
- package/dist/crypto-wasm.d.ts +100 -0
- package/dist/crypto-wasm.d.ts.map +1 -0
- package/dist/crypto-wasm.js +229 -0
- package/dist/crypto-wasm.js.map +1 -0
- package/dist/federation.d.ts +200 -0
- package/dist/federation.d.ts.map +1 -0
- package/dist/federation.js +691 -0
- package/dist/federation.js.map +1 -0
- package/dist/federation.test.d.ts +2 -0
- package/dist/federation.test.d.ts.map +1 -0
- package/dist/federation.test.js +71 -0
- package/dist/federation.test.js.map +1 -0
- package/dist/gateway/capability-router.d.ts +77 -0
- package/dist/gateway/capability-router.d.ts.map +1 -0
- package/dist/gateway/capability-router.js +222 -0
- package/dist/gateway/capability-router.js.map +1 -0
- package/dist/gateway/demand-spawner.d.ts +155 -0
- package/dist/gateway/demand-spawner.d.ts.map +1 -0
- package/dist/gateway/demand-spawner.js +426 -0
- package/dist/gateway/demand-spawner.js.map +1 -0
- package/dist/identity.d.ts +46 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +102 -0
- package/dist/identity.js.map +1 -0
- package/dist/identity.test.d.ts +2 -0
- package/dist/identity.test.d.ts.map +1 -0
- package/dist/identity.test.js +45 -0
- package/dist/identity.test.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1572 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.d.ts +210 -0
- package/dist/integration.d.ts.map +1 -0
- package/dist/integration.js +1105 -0
- package/dist/integration.js.map +1 -0
- package/dist/integration.test.d.ts +2 -0
- package/dist/integration.test.d.ts.map +1 -0
- package/dist/integration.test.js +155 -0
- package/dist/integration.test.js.map +1 -0
- package/dist/knowledge.d.ts +219 -0
- package/dist/knowledge.d.ts.map +1 -0
- package/dist/knowledge.js +543 -0
- package/dist/knowledge.js.map +1 -0
- package/dist/knowledge.test.d.ts +2 -0
- package/dist/knowledge.test.d.ts.map +1 -0
- package/dist/knowledge.test.js +72 -0
- package/dist/knowledge.test.js.map +1 -0
- package/dist/latent-space.d.ts +178 -0
- package/dist/latent-space.d.ts.map +1 -0
- package/dist/latent-space.js +385 -0
- package/dist/latent-space.js.map +1 -0
- package/dist/lib.d.ts +30 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +30 -0
- package/dist/lib.js.map +1 -0
- package/dist/mcp/server.d.ts +74 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +1392 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/metrics.d.ts +98 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +222 -0
- package/dist/metrics.js.map +1 -0
- package/dist/p2p.d.ts +87 -0
- package/dist/p2p.d.ts.map +1 -0
- package/dist/p2p.js +606 -0
- package/dist/p2p.js.map +1 -0
- package/dist/persona/capabilities.d.ts +17 -0
- package/dist/persona/capabilities.d.ts.map +1 -0
- package/dist/persona/capabilities.js +224 -0
- package/dist/persona/capabilities.js.map +1 -0
- package/dist/persona/domains.d.ts +22 -0
- package/dist/persona/domains.d.ts.map +1 -0
- package/dist/persona/domains.js +176 -0
- package/dist/persona/domains.js.map +1 -0
- package/dist/persona/embeddings.d.ts +40 -0
- package/dist/persona/embeddings.d.ts.map +1 -0
- package/dist/persona/embeddings.js +265 -0
- package/dist/persona/embeddings.js.map +1 -0
- package/dist/persona/engine.d.ts +79 -0
- package/dist/persona/engine.d.ts.map +1 -0
- package/dist/persona/engine.js +1087 -0
- package/dist/persona/engine.js.map +1 -0
- package/dist/persona/index.d.ts +11 -0
- package/dist/persona/index.d.ts.map +1 -0
- package/dist/persona/index.js +11 -0
- package/dist/persona/index.js.map +1 -0
- package/dist/persona/lifecycle.d.ts +17 -0
- package/dist/persona/lifecycle.d.ts.map +1 -0
- package/dist/persona/lifecycle.js +36 -0
- package/dist/persona/lifecycle.js.map +1 -0
- package/dist/persona/retrieval.d.ts +6 -0
- package/dist/persona/retrieval.d.ts.map +1 -0
- package/dist/persona/retrieval.js +122 -0
- package/dist/persona/retrieval.js.map +1 -0
- package/dist/persona/sync.d.ts +15 -0
- package/dist/persona/sync.d.ts.map +1 -0
- package/dist/persona/sync.js +92 -0
- package/dist/persona/sync.js.map +1 -0
- package/dist/persona/types.d.ts +283 -0
- package/dist/persona/types.d.ts.map +1 -0
- package/dist/persona/types.js +2 -0
- package/dist/persona/types.js.map +1 -0
- package/dist/persona/zkp/engine.d.ts +26 -0
- package/dist/persona/zkp/engine.d.ts.map +1 -0
- package/dist/persona/zkp/engine.js +370 -0
- package/dist/persona/zkp/engine.js.map +1 -0
- package/dist/persona/zkp/types.d.ts +39 -0
- package/dist/persona/zkp/types.d.ts.map +1 -0
- package/dist/persona/zkp/types.js +2 -0
- package/dist/persona/zkp/types.js.map +1 -0
- package/dist/planner.d.ts +114 -0
- package/dist/planner.d.ts.map +1 -0
- package/dist/planner.js +522 -0
- package/dist/planner.js.map +1 -0
- package/dist/proactive/checkpoints.d.ts +9 -0
- package/dist/proactive/checkpoints.d.ts.map +1 -0
- package/dist/proactive/checkpoints.js +20 -0
- package/dist/proactive/checkpoints.js.map +1 -0
- package/dist/proactive/engine.d.ts +59 -0
- package/dist/proactive/engine.d.ts.map +1 -0
- package/dist/proactive/engine.js +406 -0
- package/dist/proactive/engine.js.map +1 -0
- package/dist/proactive/scheduler.d.ts +11 -0
- package/dist/proactive/scheduler.d.ts.map +1 -0
- package/dist/proactive/scheduler.js +45 -0
- package/dist/proactive/scheduler.js.map +1 -0
- package/dist/proactive/swarm-controller.d.ts +189 -0
- package/dist/proactive/swarm-controller.d.ts.map +1 -0
- package/dist/proactive/swarm-controller.js +477 -0
- package/dist/proactive/swarm-controller.js.map +1 -0
- package/dist/proactive/swarm-registry.d.ts +13 -0
- package/dist/proactive/swarm-registry.d.ts.map +1 -0
- package/dist/proactive/swarm-registry.js +122 -0
- package/dist/proactive/swarm-registry.js.map +1 -0
- package/dist/proactive/types.d.ts +145 -0
- package/dist/proactive/types.d.ts.map +1 -0
- package/dist/proactive/types.js +25 -0
- package/dist/proactive/types.js.map +1 -0
- package/dist/registry.d.ts +35 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +88 -0
- package/dist/registry.js.map +1 -0
- package/dist/reputation.d.ts +123 -0
- package/dist/reputation.d.ts.map +1 -0
- package/dist/reputation.js +366 -0
- package/dist/reputation.js.map +1 -0
- package/dist/reputation.test.d.ts +5 -0
- package/dist/reputation.test.d.ts.map +1 -0
- package/dist/reputation.test.js +265 -0
- package/dist/reputation.test.js.map +1 -0
- package/dist/rooms.d.ts +96 -0
- package/dist/rooms.d.ts.map +1 -0
- package/dist/rooms.js +410 -0
- package/dist/rooms.js.map +1 -0
- package/dist/sdk/client.d.ts +290 -0
- package/dist/sdk/client.d.ts.map +1 -0
- package/dist/sdk/client.js +1287 -0
- package/dist/sdk/client.js.map +1 -0
- package/dist/sdk/index.d.ts +32 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +70 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/security.d.ts +230 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +652 -0
- package/dist/security.js.map +1 -0
- package/dist/skills/engine.d.ts +262 -0
- package/dist/skills/engine.d.ts.map +1 -0
- package/dist/skills/engine.js +788 -0
- package/dist/skills/engine.js.map +1 -0
- package/dist/skills/engine.test.d.ts +2 -0
- package/dist/skills/engine.test.d.ts.map +1 -0
- package/dist/skills/engine.test.js +134 -0
- package/dist/skills/engine.test.js.map +1 -0
- package/dist/skills/parser.d.ts +129 -0
- package/dist/skills/parser.d.ts.map +1 -0
- package/dist/skills/parser.js +318 -0
- package/dist/skills/parser.js.map +1 -0
- package/dist/social.d.ts +149 -0
- package/dist/social.d.ts.map +1 -0
- package/dist/social.js +401 -0
- package/dist/social.js.map +1 -0
- package/dist/storage-optimized.d.ts +116 -0
- package/dist/storage-optimized.d.ts.map +1 -0
- package/dist/storage-optimized.js +264 -0
- package/dist/storage-optimized.js.map +1 -0
- package/dist/storage.d.ts +584 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +2703 -0
- package/dist/storage.js.map +1 -0
- package/dist/storage.test.d.ts +2 -0
- package/dist/storage.test.d.ts.map +1 -0
- package/dist/storage.test.js +78 -0
- package/dist/storage.test.js.map +1 -0
- package/dist/swp.d.ts +443 -0
- package/dist/swp.d.ts.map +1 -0
- package/dist/swp.js +223 -0
- package/dist/swp.js.map +1 -0
- package/dist/swp.test.d.ts +5 -0
- package/dist/swp.test.d.ts.map +1 -0
- package/dist/swp.test.js +127 -0
- package/dist/swp.test.js.map +1 -0
- package/dist/templates.d.ts +25 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +1048 -0
- package/dist/templates.js.map +1 -0
- package/dist/test-e2e.d.ts +14 -0
- package/dist/test-e2e.d.ts.map +1 -0
- package/dist/test-e2e.js +266 -0
- package/dist/test-e2e.js.map +1 -0
- package/dist/workers/research-worker.d.ts +19 -0
- package/dist/workers/research-worker.d.ts.map +1 -0
- package/dist/workers/research-worker.js +141 -0
- package/dist/workers/research-worker.js.map +1 -0
- package/package.json +110 -0
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Society Protocol - Federation System v1.0
|
|
3
|
+
*
|
|
4
|
+
* Sistema de federações inspirado no Matrix Protocol
|
|
5
|
+
* - Federações permissionadas com governança
|
|
6
|
+
* - Políticas customizáveis por federação
|
|
7
|
+
* - Homeservers e rooms hierárquicos
|
|
8
|
+
* - ACLs e permissões granulares
|
|
9
|
+
* - social.md para definição de federações
|
|
10
|
+
*/
|
|
11
|
+
import { EventEmitter } from 'events';
|
|
12
|
+
import { ulid } from 'ulid';
|
|
13
|
+
import YAML from 'yaml';
|
|
14
|
+
// ─── Federation Engine ───────────────────────────────────────────
|
|
15
|
+
export class FederationEngine extends EventEmitter {
|
|
16
|
+
storage;
|
|
17
|
+
identity;
|
|
18
|
+
federations = new Map();
|
|
19
|
+
memberFederations = new Map(); // did -> federations
|
|
20
|
+
proposals = new Map();
|
|
21
|
+
peerings = new Map();
|
|
22
|
+
constructor(storage, identity) {
|
|
23
|
+
super();
|
|
24
|
+
this.storage = storage;
|
|
25
|
+
this.identity = identity;
|
|
26
|
+
this.loadFederations();
|
|
27
|
+
this.loadGovernance();
|
|
28
|
+
this.loadPeerings();
|
|
29
|
+
}
|
|
30
|
+
// ─── Federation CRUD ─────────────────────────────────────────
|
|
31
|
+
async createFederation(name, description, visibility = 'private', manifest) {
|
|
32
|
+
const id = `fed_${ulid()}`;
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
const federation = {
|
|
35
|
+
id,
|
|
36
|
+
name,
|
|
37
|
+
description,
|
|
38
|
+
visibility,
|
|
39
|
+
did: `did:society:${id}`,
|
|
40
|
+
creator: this.identity.did,
|
|
41
|
+
createdAt: now,
|
|
42
|
+
governance: manifest?.federation.governance ? {
|
|
43
|
+
model: manifest.federation.governance,
|
|
44
|
+
admins: [this.identity.did],
|
|
45
|
+
moderators: [],
|
|
46
|
+
policyChangeThreshold: 51,
|
|
47
|
+
electionEnabled: manifest.federation.governance === 'democracy',
|
|
48
|
+
votingPower: 'reputation'
|
|
49
|
+
} : {
|
|
50
|
+
model: 'dictatorship',
|
|
51
|
+
admins: [this.identity.did],
|
|
52
|
+
moderators: [],
|
|
53
|
+
policyChangeThreshold: 100,
|
|
54
|
+
electionEnabled: false,
|
|
55
|
+
votingPower: 'equal'
|
|
56
|
+
},
|
|
57
|
+
policies: manifest?.policies.map((p) => ({
|
|
58
|
+
id: `pol_${ulid()}`,
|
|
59
|
+
name: p.name,
|
|
60
|
+
description: '',
|
|
61
|
+
type: p.type,
|
|
62
|
+
resource: p.resource,
|
|
63
|
+
conditions: p.conditions,
|
|
64
|
+
createdAt: now,
|
|
65
|
+
createdBy: this.identity.did
|
|
66
|
+
})) || [this.getDefaultPolicy()],
|
|
67
|
+
roles: manifest?.roles.map((r, i) => ({
|
|
68
|
+
id: `role_${i}`,
|
|
69
|
+
name: r.name,
|
|
70
|
+
description: '',
|
|
71
|
+
permissions: r.permissions,
|
|
72
|
+
hierarchy: r.hierarchy
|
|
73
|
+
})) || this.getDefaultRoles(),
|
|
74
|
+
members: new Map(),
|
|
75
|
+
memberCount: 1,
|
|
76
|
+
onlineCount: 1,
|
|
77
|
+
rooms: [],
|
|
78
|
+
settings: manifest?.settings ? {
|
|
79
|
+
allowGuestAccess: manifest.settings.allowGuestAccess,
|
|
80
|
+
requireInvitation: manifest.settings.requireInvitation,
|
|
81
|
+
autoVerifyMembers: false,
|
|
82
|
+
messageRetentionDays: manifest.settings.messageRetentionDays,
|
|
83
|
+
maxRoomSize: manifest.settings.maxRoomSize,
|
|
84
|
+
federationOutbound: true,
|
|
85
|
+
federationInbound: visibility === 'public'
|
|
86
|
+
} : {
|
|
87
|
+
allowGuestAccess: false,
|
|
88
|
+
requireInvitation: visibility !== 'public',
|
|
89
|
+
autoVerifyMembers: false,
|
|
90
|
+
messageRetentionDays: 30,
|
|
91
|
+
maxRoomSize: 1000,
|
|
92
|
+
federationOutbound: true,
|
|
93
|
+
federationInbound: visibility === 'public'
|
|
94
|
+
},
|
|
95
|
+
encryption: {
|
|
96
|
+
enabled: visibility !== 'public',
|
|
97
|
+
algorithm: 'x25519-xsalsa20-poly1305',
|
|
98
|
+
keyRotationDays: 30
|
|
99
|
+
},
|
|
100
|
+
tags: [],
|
|
101
|
+
language: 'pt-BR',
|
|
102
|
+
timezone: 'America/Sao_Paulo'
|
|
103
|
+
};
|
|
104
|
+
// Criador é admin automaticamente
|
|
105
|
+
federation.members.set(this.identity.did, {
|
|
106
|
+
did: this.identity.did,
|
|
107
|
+
displayName: this.identity.displayName,
|
|
108
|
+
status: 'admin',
|
|
109
|
+
role: 'admin',
|
|
110
|
+
joinedAt: now,
|
|
111
|
+
reputationAtJoin: 100,
|
|
112
|
+
lastActivity: now
|
|
113
|
+
});
|
|
114
|
+
this.federations.set(id, federation);
|
|
115
|
+
this.addMemberToIndex(this.identity.did, id);
|
|
116
|
+
await this.saveFederation(federation);
|
|
117
|
+
this.emit('federation:created', federation);
|
|
118
|
+
return federation;
|
|
119
|
+
}
|
|
120
|
+
async joinFederation(federationId, did, displayName, inviterDid) {
|
|
121
|
+
const federation = this.federations.get(federationId);
|
|
122
|
+
if (!federation)
|
|
123
|
+
throw new Error('Federation not found');
|
|
124
|
+
// Verificar se já é membro
|
|
125
|
+
if (federation.members.has(did)) {
|
|
126
|
+
throw new Error('Already a member');
|
|
127
|
+
}
|
|
128
|
+
// Verificar políticas de entrada
|
|
129
|
+
if (federation.settings.requireInvitation && !inviterDid) {
|
|
130
|
+
throw new Error('Invitation required');
|
|
131
|
+
}
|
|
132
|
+
if (federation.visibility === 'invite-only' && !inviterDid) {
|
|
133
|
+
throw new Error('Invitation required for invite-only federation');
|
|
134
|
+
}
|
|
135
|
+
// Verificar ban
|
|
136
|
+
const existingMember = federation.members.get(did);
|
|
137
|
+
if (existingMember?.status === 'banned') {
|
|
138
|
+
throw new Error('Banned from federation');
|
|
139
|
+
}
|
|
140
|
+
// Fetch actual reputation from storage
|
|
141
|
+
let reputationAtJoin = 0;
|
|
142
|
+
try {
|
|
143
|
+
const repRecord = this.storage.getReputationRecord?.(did);
|
|
144
|
+
if (repRecord?.overall_score) {
|
|
145
|
+
reputationAtJoin = repRecord.overall_score;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// Storage may not have reputation data yet; default to 0
|
|
150
|
+
}
|
|
151
|
+
const member = {
|
|
152
|
+
did,
|
|
153
|
+
displayName,
|
|
154
|
+
status: federation.settings.autoVerifyMembers ? 'member' : 'pending',
|
|
155
|
+
role: 'member',
|
|
156
|
+
joinedAt: Date.now(),
|
|
157
|
+
invitedBy: inviterDid,
|
|
158
|
+
reputationAtJoin,
|
|
159
|
+
lastActivity: Date.now()
|
|
160
|
+
};
|
|
161
|
+
federation.members.set(did, member);
|
|
162
|
+
federation.memberCount++;
|
|
163
|
+
this.addMemberToIndex(did, federationId);
|
|
164
|
+
await this.saveFederation(federation);
|
|
165
|
+
this.emit('federation:member:joined', federationId, member);
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
async leaveFederation(federationId, did) {
|
|
169
|
+
const federation = this.federations.get(federationId);
|
|
170
|
+
if (!federation)
|
|
171
|
+
throw new Error('Federation not found');
|
|
172
|
+
if (!federation.members.has(did)) {
|
|
173
|
+
throw new Error('Not a member');
|
|
174
|
+
}
|
|
175
|
+
// Não permitir se for o último admin
|
|
176
|
+
const member = federation.members.get(did);
|
|
177
|
+
if (member.status === 'admin') {
|
|
178
|
+
const adminCount = Array.from(federation.members.values())
|
|
179
|
+
.filter(m => m.status === 'admin').length;
|
|
180
|
+
if (adminCount <= 1) {
|
|
181
|
+
throw new Error('Cannot leave: last admin must transfer ownership');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
federation.members.delete(did);
|
|
185
|
+
federation.memberCount--;
|
|
186
|
+
this.removeMemberFromIndex(did, federationId);
|
|
187
|
+
await this.saveFederation(federation);
|
|
188
|
+
this.emit('federation:member:left', federationId, did);
|
|
189
|
+
}
|
|
190
|
+
// ─── Governance ─────────────────────────────────────────────
|
|
191
|
+
async proposePolicyChange(federationId, policy, proposerDid) {
|
|
192
|
+
const federation = this.federations.get(federationId);
|
|
193
|
+
if (!federation)
|
|
194
|
+
throw new Error('Federation not found');
|
|
195
|
+
// Verificar permissão
|
|
196
|
+
if (!this.hasPermission(federation, proposerDid, 'federation:policy:propose')) {
|
|
197
|
+
throw new Error('No permission to propose policy changes');
|
|
198
|
+
}
|
|
199
|
+
const proposalId = `prop_${ulid()}`;
|
|
200
|
+
const now = Date.now();
|
|
201
|
+
const proposal = {
|
|
202
|
+
id: proposalId,
|
|
203
|
+
federationId,
|
|
204
|
+
proposerDid,
|
|
205
|
+
status: 'open',
|
|
206
|
+
createdAt: now,
|
|
207
|
+
policy: {
|
|
208
|
+
id: policy.id || `pol_${ulid()}`,
|
|
209
|
+
name: policy.name || 'Policy Update',
|
|
210
|
+
description: policy.description || '',
|
|
211
|
+
type: policy.type || 'require',
|
|
212
|
+
resource: policy.resource || '*',
|
|
213
|
+
conditions: policy.conditions,
|
|
214
|
+
createdAt: now,
|
|
215
|
+
createdBy: proposerDid
|
|
216
|
+
},
|
|
217
|
+
votes: new Map()
|
|
218
|
+
};
|
|
219
|
+
this.proposals.set(proposalId, proposal);
|
|
220
|
+
this.storage.saveFederationProposal(proposalId, federationId, proposerDid, proposal.policy, 'open');
|
|
221
|
+
this.emit('federation:policy:proposed', federationId, proposalId, policy);
|
|
222
|
+
return proposalId;
|
|
223
|
+
}
|
|
224
|
+
async voteOnProposal(federationId, proposalId, voterDid, vote) {
|
|
225
|
+
const federation = this.federations.get(federationId);
|
|
226
|
+
if (!federation)
|
|
227
|
+
throw new Error('Federation not found');
|
|
228
|
+
// Calcular poder de voto
|
|
229
|
+
const votingPower = this.calculateVotingPower(federation, voterDid);
|
|
230
|
+
const proposal = this.getProposal(proposalId);
|
|
231
|
+
if (!proposal || proposal.federationId !== federationId) {
|
|
232
|
+
throw new Error('Proposal not found');
|
|
233
|
+
}
|
|
234
|
+
if (proposal.status !== 'open') {
|
|
235
|
+
throw new Error('Proposal is closed');
|
|
236
|
+
}
|
|
237
|
+
if (!federation.members.has(voterDid)) {
|
|
238
|
+
throw new Error('Only federation members can vote');
|
|
239
|
+
}
|
|
240
|
+
const voteRecord = {
|
|
241
|
+
voterDid,
|
|
242
|
+
vote,
|
|
243
|
+
votingPower,
|
|
244
|
+
votedAt: Date.now()
|
|
245
|
+
};
|
|
246
|
+
proposal.votes.set(voterDid, voteRecord);
|
|
247
|
+
this.storage.saveFederationVote(proposalId, voterDid, vote, votingPower);
|
|
248
|
+
this.evaluateProposal(federation, proposal);
|
|
249
|
+
this.emit('federation:vote', federationId, proposalId, voterDid, vote, votingPower);
|
|
250
|
+
}
|
|
251
|
+
// ─── Federation Mesh: Peering ───────────────────────────────
|
|
252
|
+
async requestPeering(sourceFederationId, targetFederationDid, policy = {}) {
|
|
253
|
+
const federation = this.federations.get(sourceFederationId);
|
|
254
|
+
if (!federation) {
|
|
255
|
+
throw new Error(`Federation not found: ${sourceFederationId}`);
|
|
256
|
+
}
|
|
257
|
+
if (!federation.settings.federationOutbound) {
|
|
258
|
+
throw new Error('Federation outbound peering is disabled');
|
|
259
|
+
}
|
|
260
|
+
if (!this.hasPermission(federation, this.identity.did, 'federation:peer:request')) {
|
|
261
|
+
throw new Error('No permission to request peering');
|
|
262
|
+
}
|
|
263
|
+
if (targetFederationDid === federation.did) {
|
|
264
|
+
throw new Error('Cannot peer federation with itself');
|
|
265
|
+
}
|
|
266
|
+
const now = Date.now();
|
|
267
|
+
const peering = {
|
|
268
|
+
id: `peer_${ulid()}`,
|
|
269
|
+
sourceFederationId,
|
|
270
|
+
sourceFederationDid: federation.did,
|
|
271
|
+
targetFederationDid,
|
|
272
|
+
requestedBy: this.identity.did,
|
|
273
|
+
status: 'pending',
|
|
274
|
+
policy: this.normalizePeeringPolicy(policy),
|
|
275
|
+
createdAt: now,
|
|
276
|
+
updatedAt: now
|
|
277
|
+
};
|
|
278
|
+
this.peerings.set(peering.id, peering);
|
|
279
|
+
this.persistPeering(peering);
|
|
280
|
+
this.emit('federation:peering:requested', peering);
|
|
281
|
+
return peering;
|
|
282
|
+
}
|
|
283
|
+
async respondPeering(peeringId, accepted, reason) {
|
|
284
|
+
const peering = this.peerings.get(peeringId);
|
|
285
|
+
if (!peering) {
|
|
286
|
+
throw new Error(`Peering not found: ${peeringId}`);
|
|
287
|
+
}
|
|
288
|
+
if (peering.status !== 'pending') {
|
|
289
|
+
throw new Error(`Peering ${peeringId} is not pending`);
|
|
290
|
+
}
|
|
291
|
+
const now = Date.now();
|
|
292
|
+
peering.status = accepted ? 'active' : 'rejected';
|
|
293
|
+
peering.reason = reason;
|
|
294
|
+
peering.respondedAt = now;
|
|
295
|
+
peering.updatedAt = now;
|
|
296
|
+
this.peerings.set(peering.id, peering);
|
|
297
|
+
this.persistPeering(peering);
|
|
298
|
+
this.emit(accepted ? 'federation:peering:accepted' : 'federation:peering:rejected', peering);
|
|
299
|
+
return peering;
|
|
300
|
+
}
|
|
301
|
+
async revokePeering(peeringId, reason) {
|
|
302
|
+
const peering = this.peerings.get(peeringId);
|
|
303
|
+
if (!peering) {
|
|
304
|
+
throw new Error(`Peering not found: ${peeringId}`);
|
|
305
|
+
}
|
|
306
|
+
const now = Date.now();
|
|
307
|
+
peering.status = 'revoked';
|
|
308
|
+
peering.reason = reason;
|
|
309
|
+
peering.respondedAt = peering.respondedAt ?? now;
|
|
310
|
+
peering.updatedAt = now;
|
|
311
|
+
this.peerings.set(peering.id, peering);
|
|
312
|
+
this.persistPeering(peering);
|
|
313
|
+
this.emit('federation:peering:revoked', peering);
|
|
314
|
+
return peering;
|
|
315
|
+
}
|
|
316
|
+
listPeerings(federationId, status) {
|
|
317
|
+
const federationDid = this.federations.get(federationId)?.did;
|
|
318
|
+
if (!federationDid) {
|
|
319
|
+
return [];
|
|
320
|
+
}
|
|
321
|
+
return Array.from(this.peerings.values())
|
|
322
|
+
.filter((peering) => {
|
|
323
|
+
if (status && peering.status !== status)
|
|
324
|
+
return false;
|
|
325
|
+
return (peering.sourceFederationId === federationId ||
|
|
326
|
+
peering.targetFederationDid === federationDid);
|
|
327
|
+
})
|
|
328
|
+
.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
329
|
+
}
|
|
330
|
+
getPeering(peeringId) {
|
|
331
|
+
return this.peerings.get(peeringId);
|
|
332
|
+
}
|
|
333
|
+
// ─── Permissions & ACL ─────────────────────────────────────
|
|
334
|
+
hasPermission(federation, did, permission) {
|
|
335
|
+
const member = federation.members.get(did);
|
|
336
|
+
if (!member)
|
|
337
|
+
return false;
|
|
338
|
+
// Admin tem todas as permissões
|
|
339
|
+
if (member.status === 'admin')
|
|
340
|
+
return true;
|
|
341
|
+
// Verificar role
|
|
342
|
+
const role = federation.roles.find(r => r.id === member.role);
|
|
343
|
+
if (!role)
|
|
344
|
+
return false;
|
|
345
|
+
return role.permissions.includes(permission) ||
|
|
346
|
+
role.permissions.includes('*');
|
|
347
|
+
}
|
|
348
|
+
checkPolicy(federation, action, actorDid, context) {
|
|
349
|
+
// Buscar políticas aplicáveis
|
|
350
|
+
const policies = federation.policies.filter(p => p.resource === action ||
|
|
351
|
+
p.resource === '*' ||
|
|
352
|
+
action.startsWith(p.resource.replace('*', '')));
|
|
353
|
+
// Ordenar: deny primeiro, depois require, depois allow
|
|
354
|
+
policies.sort((a, b) => {
|
|
355
|
+
const order = { deny: 0, require: 1, allow: 2 };
|
|
356
|
+
return order[a.type] - order[b.type];
|
|
357
|
+
});
|
|
358
|
+
for (const policy of policies) {
|
|
359
|
+
// Verificar condições
|
|
360
|
+
if (policy.conditions) {
|
|
361
|
+
const member = federation.members.get(actorDid);
|
|
362
|
+
if (!member)
|
|
363
|
+
continue;
|
|
364
|
+
if (policy.conditions.minReputation) {
|
|
365
|
+
// TODO: Verificar reputação real
|
|
366
|
+
}
|
|
367
|
+
if (policy.conditions.allowedRoles) {
|
|
368
|
+
if (!policy.conditions.allowedRoles.includes(member.role)) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
switch (policy.type) {
|
|
374
|
+
case 'deny':
|
|
375
|
+
return { allowed: false, reason: `Policy denies: ${policy.name}` };
|
|
376
|
+
case 'require':
|
|
377
|
+
// Verificar se requisito está satisfeito
|
|
378
|
+
break;
|
|
379
|
+
case 'allow':
|
|
380
|
+
return { allowed: true };
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// Default: deny
|
|
384
|
+
return { allowed: false, reason: 'No policy allows this action' };
|
|
385
|
+
}
|
|
386
|
+
// ─── social.md Parser ───────────────────────────────────────
|
|
387
|
+
static parseSocialManifest(yamlContent) {
|
|
388
|
+
const parsed = YAML.parse(yamlContent);
|
|
389
|
+
if (!parsed?.social?.name || !parsed?.social?.description) {
|
|
390
|
+
throw new Error('Invalid social manifest: social.name and social.description are required');
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
social: {
|
|
394
|
+
name: parsed.social.name,
|
|
395
|
+
version: parsed.social.version || '1.0.0',
|
|
396
|
+
description: parsed.social.description,
|
|
397
|
+
author: parsed.social.author || 'unknown',
|
|
398
|
+
created: parsed.social.created || new Date().toISOString()
|
|
399
|
+
},
|
|
400
|
+
federation: {
|
|
401
|
+
visibility: parsed.federation?.visibility || 'private',
|
|
402
|
+
governance: parsed.federation?.governance || 'dictatorship',
|
|
403
|
+
homeserver: parsed.federation?.homeserver
|
|
404
|
+
},
|
|
405
|
+
policies: parsed.policies || [],
|
|
406
|
+
roles: parsed.roles || [],
|
|
407
|
+
settings: {
|
|
408
|
+
allowGuestAccess: parsed.settings?.allowGuestAccess ?? false,
|
|
409
|
+
requireInvitation: parsed.settings?.requireInvitation ?? true,
|
|
410
|
+
messageRetentionDays: parsed.settings?.messageRetentionDays ?? 30,
|
|
411
|
+
maxRoomSize: parsed.settings?.maxRoomSize ?? 1000
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
async createFederationFromSocial(socialPath) {
|
|
416
|
+
// Ler arquivo
|
|
417
|
+
const fs = await import('fs');
|
|
418
|
+
const content = fs.readFileSync(socialPath, 'utf-8');
|
|
419
|
+
const manifest = FederationEngine.parseSocialManifest(content);
|
|
420
|
+
return this.createFederation(manifest.social.name, manifest.social.description, manifest.federation.visibility, manifest);
|
|
421
|
+
}
|
|
422
|
+
// ─── Queries ────────────────────────────────────────────────
|
|
423
|
+
getFederation(id) {
|
|
424
|
+
return this.federations.get(id);
|
|
425
|
+
}
|
|
426
|
+
getMemberFederations(did) {
|
|
427
|
+
const ids = this.memberFederations.get(did);
|
|
428
|
+
if (!ids)
|
|
429
|
+
return [];
|
|
430
|
+
return Array.from(ids).map(id => this.federations.get(id)).filter(Boolean);
|
|
431
|
+
}
|
|
432
|
+
getPublicFederations() {
|
|
433
|
+
return Array.from(this.federations.values())
|
|
434
|
+
.filter(f => f.visibility === 'public');
|
|
435
|
+
}
|
|
436
|
+
searchFederations(query) {
|
|
437
|
+
const lowerQuery = query.toLowerCase();
|
|
438
|
+
return Array.from(this.federations.values())
|
|
439
|
+
.filter(f => f.name.toLowerCase().includes(lowerQuery) ||
|
|
440
|
+
f.description.toLowerCase().includes(lowerQuery) ||
|
|
441
|
+
f.tags.some(t => t.toLowerCase().includes(lowerQuery)));
|
|
442
|
+
}
|
|
443
|
+
// ─── Private Helpers ────────────────────────────────────────
|
|
444
|
+
getDefaultPolicy() {
|
|
445
|
+
return {
|
|
446
|
+
id: `pol_${ulid()}`,
|
|
447
|
+
name: 'Default Allow',
|
|
448
|
+
description: 'Allow all actions by default',
|
|
449
|
+
type: 'allow',
|
|
450
|
+
resource: '*',
|
|
451
|
+
createdAt: Date.now(),
|
|
452
|
+
createdBy: this.identity.did
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
getDefaultRoles() {
|
|
456
|
+
return [
|
|
457
|
+
{
|
|
458
|
+
id: 'admin',
|
|
459
|
+
name: 'Administrator',
|
|
460
|
+
description: 'Full control of federation',
|
|
461
|
+
permissions: ['*'],
|
|
462
|
+
hierarchy: 100,
|
|
463
|
+
color: '#ff0000'
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
id: 'moderator',
|
|
467
|
+
name: 'Moderator',
|
|
468
|
+
description: 'Can moderate content and members',
|
|
469
|
+
permissions: [
|
|
470
|
+
'room:moderate',
|
|
471
|
+
'member:ban',
|
|
472
|
+
'message:delete',
|
|
473
|
+
'federation:policy:propose'
|
|
474
|
+
],
|
|
475
|
+
hierarchy: 50,
|
|
476
|
+
color: '#00ff00'
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
id: 'member',
|
|
480
|
+
name: 'Member',
|
|
481
|
+
description: 'Regular member',
|
|
482
|
+
permissions: [
|
|
483
|
+
'room:join',
|
|
484
|
+
'room:create',
|
|
485
|
+
'message:send',
|
|
486
|
+
'member:invite'
|
|
487
|
+
],
|
|
488
|
+
hierarchy: 10,
|
|
489
|
+
color: '#0000ff'
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
id: 'guest',
|
|
493
|
+
name: 'Guest',
|
|
494
|
+
description: 'Limited access',
|
|
495
|
+
permissions: [
|
|
496
|
+
'room:join',
|
|
497
|
+
'message:send:readonly'
|
|
498
|
+
],
|
|
499
|
+
hierarchy: 0,
|
|
500
|
+
color: '#808080'
|
|
501
|
+
}
|
|
502
|
+
];
|
|
503
|
+
}
|
|
504
|
+
normalizePeeringPolicy(policy) {
|
|
505
|
+
const allowedTypes = policy.allowedTypes?.length
|
|
506
|
+
? policy.allowedTypes
|
|
507
|
+
: ['chat.msg', 'coc.open', 'coc.plan', 'coc.submit', 'federation.bridge.sync'];
|
|
508
|
+
const maxRatePerMinute = Number(policy.maxRatePerMinute);
|
|
509
|
+
return {
|
|
510
|
+
allowedTypes,
|
|
511
|
+
maxRatePerMinute: Number.isFinite(maxRatePerMinute) && maxRatePerMinute > 0
|
|
512
|
+
? Math.floor(maxRatePerMinute)
|
|
513
|
+
: 200,
|
|
514
|
+
privacyMode: policy.privacyMode || 'summary',
|
|
515
|
+
allowedRooms: policy.allowedRooms,
|
|
516
|
+
blockedRooms: policy.blockedRooms
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
calculateVotingPower(federation, did) {
|
|
520
|
+
const member = federation.members.get(did);
|
|
521
|
+
if (!member)
|
|
522
|
+
return 0;
|
|
523
|
+
switch (federation.governance.votingPower) {
|
|
524
|
+
case 'equal':
|
|
525
|
+
return 1;
|
|
526
|
+
case 'reputation':
|
|
527
|
+
const reputation = this.storage.getReputationRecord?.(did);
|
|
528
|
+
if (reputation?.overall_score) {
|
|
529
|
+
return Math.max(0.1, reputation.overall_score);
|
|
530
|
+
}
|
|
531
|
+
return Math.max(0.1, member.reputationAtJoin || 1);
|
|
532
|
+
case 'stake':
|
|
533
|
+
// Stake ainda não está disponível - fallback para peso igual
|
|
534
|
+
return 1;
|
|
535
|
+
default:
|
|
536
|
+
return 1;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
addMemberToIndex(did, federationId) {
|
|
540
|
+
if (!this.memberFederations.has(did)) {
|
|
541
|
+
this.memberFederations.set(did, new Set());
|
|
542
|
+
}
|
|
543
|
+
this.memberFederations.get(did).add(federationId);
|
|
544
|
+
}
|
|
545
|
+
removeMemberFromIndex(did, federationId) {
|
|
546
|
+
this.memberFederations.get(did)?.delete(federationId);
|
|
547
|
+
}
|
|
548
|
+
async saveFederation(federation) {
|
|
549
|
+
this.storage.saveFederation?.(federation);
|
|
550
|
+
}
|
|
551
|
+
loadFederations() {
|
|
552
|
+
const federations = this.storage.getFederations?.() || [];
|
|
553
|
+
for (const fed of federations) {
|
|
554
|
+
const hydrated = this.hydrateFederation(fed);
|
|
555
|
+
this.federations.set(hydrated.id, hydrated);
|
|
556
|
+
for (const [did] of hydrated.members) {
|
|
557
|
+
this.addMemberToIndex(did, fed.id);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
loadGovernance() {
|
|
562
|
+
const proposals = this.storage.getFederationProposals?.() || [];
|
|
563
|
+
for (const proposalRow of proposals) {
|
|
564
|
+
const proposal = {
|
|
565
|
+
id: proposalRow.proposal_id,
|
|
566
|
+
federationId: proposalRow.federation_id,
|
|
567
|
+
proposerDid: proposalRow.proposer_did,
|
|
568
|
+
policy: proposalRow.policy,
|
|
569
|
+
status: proposalRow.status,
|
|
570
|
+
createdAt: proposalRow.created_at,
|
|
571
|
+
closedAt: proposalRow.closed_at || undefined,
|
|
572
|
+
votes: new Map()
|
|
573
|
+
};
|
|
574
|
+
const votes = this.storage.getFederationVotes?.(proposal.id) || [];
|
|
575
|
+
for (const vote of votes) {
|
|
576
|
+
proposal.votes.set(vote.voter_did, {
|
|
577
|
+
voterDid: vote.voter_did,
|
|
578
|
+
vote: vote.vote,
|
|
579
|
+
votingPower: vote.voting_power,
|
|
580
|
+
votedAt: vote.voted_at
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
this.proposals.set(proposal.id, proposal);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
loadPeerings() {
|
|
587
|
+
const rows = this.storage.listFederationPeerings?.() || [];
|
|
588
|
+
for (const row of rows) {
|
|
589
|
+
const storedPolicy = (row.policy || {});
|
|
590
|
+
const peering = {
|
|
591
|
+
id: row.peeringId,
|
|
592
|
+
sourceFederationId: row.sourceFederationId,
|
|
593
|
+
sourceFederationDid: row.sourceFederationDid,
|
|
594
|
+
targetFederationDid: row.targetFederationDid,
|
|
595
|
+
requestedBy: typeof storedPolicy.requestedBy === 'string'
|
|
596
|
+
? storedPolicy.requestedBy
|
|
597
|
+
: row.sourceFederationDid,
|
|
598
|
+
status: row.status,
|
|
599
|
+
policy: this.normalizePeeringPolicy(storedPolicy),
|
|
600
|
+
reason: row.reason,
|
|
601
|
+
createdAt: row.createdAt,
|
|
602
|
+
updatedAt: row.updatedAt,
|
|
603
|
+
respondedAt: row.respondedAt
|
|
604
|
+
};
|
|
605
|
+
this.peerings.set(peering.id, peering);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
persistPeering(peering) {
|
|
609
|
+
this.storage.saveFederationPeering?.({
|
|
610
|
+
peeringId: peering.id,
|
|
611
|
+
sourceFederationId: peering.sourceFederationId,
|
|
612
|
+
sourceFederationDid: peering.sourceFederationDid,
|
|
613
|
+
targetFederationDid: peering.targetFederationDid,
|
|
614
|
+
policy: {
|
|
615
|
+
...peering.policy,
|
|
616
|
+
requestedBy: peering.requestedBy
|
|
617
|
+
},
|
|
618
|
+
status: peering.status,
|
|
619
|
+
reason: peering.reason,
|
|
620
|
+
createdAt: peering.createdAt,
|
|
621
|
+
updatedAt: peering.updatedAt,
|
|
622
|
+
respondedAt: peering.respondedAt
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
getProposal(proposalId) {
|
|
626
|
+
return this.proposals.get(proposalId);
|
|
627
|
+
}
|
|
628
|
+
evaluateProposal(federation, proposal) {
|
|
629
|
+
if (proposal.status !== 'open')
|
|
630
|
+
return;
|
|
631
|
+
const eligibleMembers = Array.from(federation.members.values())
|
|
632
|
+
.filter((member) => member.status !== 'pending' && member.status !== 'banned');
|
|
633
|
+
const totalEligiblePower = eligibleMembers.reduce((acc, member) => acc + this.calculateVotingPower(federation, member.did), 0);
|
|
634
|
+
if (totalEligiblePower <= 0) {
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
let yesPower = 0;
|
|
638
|
+
let noPower = 0;
|
|
639
|
+
for (const vote of proposal.votes.values()) {
|
|
640
|
+
if (vote.vote === 'yes')
|
|
641
|
+
yesPower += vote.votingPower;
|
|
642
|
+
if (vote.vote === 'no')
|
|
643
|
+
noPower += vote.votingPower;
|
|
644
|
+
}
|
|
645
|
+
const threshold = federation.governance.policyChangeThreshold / 100;
|
|
646
|
+
if (yesPower / totalEligiblePower >= threshold) {
|
|
647
|
+
const policyIndex = federation.policies.findIndex(p => p.id === proposal.policy.id);
|
|
648
|
+
if (policyIndex >= 0) {
|
|
649
|
+
federation.policies[policyIndex] = proposal.policy;
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
federation.policies.push(proposal.policy);
|
|
653
|
+
}
|
|
654
|
+
proposal.status = 'approved';
|
|
655
|
+
proposal.closedAt = Date.now();
|
|
656
|
+
this.storage.setFederationProposalStatus?.(proposal.id, 'approved');
|
|
657
|
+
this.saveFederation(federation);
|
|
658
|
+
this.emit('federation:policy:approved', federation.id, proposal.id, proposal.policy);
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
if (noPower / totalEligiblePower > (1 - threshold)) {
|
|
662
|
+
proposal.status = 'rejected';
|
|
663
|
+
proposal.closedAt = Date.now();
|
|
664
|
+
this.storage.setFederationProposalStatus?.(proposal.id, 'rejected');
|
|
665
|
+
this.emit('federation:policy:rejected', federation.id, proposal.id);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
hydrateFederation(raw) {
|
|
669
|
+
const members = this.normalizeMembers(raw.members);
|
|
670
|
+
return {
|
|
671
|
+
...raw,
|
|
672
|
+
members,
|
|
673
|
+
memberCount: raw.memberCount ?? members.size,
|
|
674
|
+
onlineCount: raw.onlineCount ?? 0
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
normalizeMembers(rawMembers) {
|
|
678
|
+
if (rawMembers instanceof Map) {
|
|
679
|
+
return rawMembers;
|
|
680
|
+
}
|
|
681
|
+
if (Array.isArray(rawMembers)) {
|
|
682
|
+
return new Map(rawMembers);
|
|
683
|
+
}
|
|
684
|
+
if (rawMembers && typeof rawMembers === 'object') {
|
|
685
|
+
return new Map(Object.entries(rawMembers));
|
|
686
|
+
}
|
|
687
|
+
return new Map();
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
// Classes already exported via 'export class'
|
|
691
|
+
//# sourceMappingURL=federation.js.map
|