chainlesschain 0.37.12 → 0.40.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/package.json +3 -2
- package/src/commands/agent.js +7 -1
- package/src/commands/ask.js +24 -9
- package/src/commands/chat.js +7 -1
- package/src/commands/cli-anything.js +266 -0
- package/src/commands/compliance.js +216 -0
- package/src/commands/dao.js +312 -0
- package/src/commands/dlp.js +278 -0
- package/src/commands/evomap.js +558 -0
- package/src/commands/hardening.js +230 -0
- package/src/commands/matrix.js +168 -0
- package/src/commands/nostr.js +185 -0
- package/src/commands/pqc.js +162 -0
- package/src/commands/scim.js +218 -0
- package/src/commands/serve.js +109 -0
- package/src/commands/siem.js +156 -0
- package/src/commands/social.js +480 -0
- package/src/commands/terraform.js +148 -0
- package/src/constants.js +1 -0
- package/src/index.js +60 -0
- package/src/lib/autonomous-agent.js +487 -0
- package/src/lib/cli-anything-bridge.js +379 -0
- package/src/lib/cli-context-engineering.js +472 -0
- package/src/lib/compliance-manager.js +290 -0
- package/src/lib/content-recommender.js +205 -0
- package/src/lib/dao-governance.js +296 -0
- package/src/lib/dlp-engine.js +304 -0
- package/src/lib/evomap-client.js +135 -0
- package/src/lib/evomap-federation.js +240 -0
- package/src/lib/evomap-governance.js +250 -0
- package/src/lib/evomap-manager.js +227 -0
- package/src/lib/git-integration.js +1 -1
- package/src/lib/hardening-manager.js +275 -0
- package/src/lib/llm-providers.js +14 -1
- package/src/lib/matrix-bridge.js +196 -0
- package/src/lib/nostr-bridge.js +195 -0
- package/src/lib/permanent-memory.js +370 -0
- package/src/lib/plan-mode.js +211 -0
- package/src/lib/pqc-manager.js +196 -0
- package/src/lib/scim-manager.js +212 -0
- package/src/lib/session-manager.js +38 -0
- package/src/lib/siem-exporter.js +137 -0
- package/src/lib/social-manager.js +283 -0
- package/src/lib/task-model-selector.js +232 -0
- package/src/lib/terraform-manager.js +201 -0
- package/src/lib/ws-server.js +474 -0
- package/src/repl/agent-repl.js +796 -41
- package/src/repl/chat-repl.js +14 -6
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EvoMap Client — GEP-A2A HTTP client for gene exchange.
|
|
3
|
+
*
|
|
4
|
+
* Communicates with EvoMap Hub servers to search, publish, and download
|
|
5
|
+
* agent capability genes (skills, prompts, tool configs).
|
|
6
|
+
*
|
|
7
|
+
* Lightweight: uses native fetch, no heavy dependencies.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Exported for test injection
|
|
11
|
+
export const _deps = {
|
|
12
|
+
fetch: globalThis.fetch,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const DEFAULT_HUB = "https://evomap.chainlesschain.com/api/v1";
|
|
16
|
+
|
|
17
|
+
export class EvoMapClient {
|
|
18
|
+
/**
|
|
19
|
+
* @param {object} options
|
|
20
|
+
* @param {string} [options.hubUrl] - EvoMap Hub URL
|
|
21
|
+
* @param {string} [options.apiKey] - Optional API key for authenticated endpoints
|
|
22
|
+
* @param {number} [options.timeout] - Request timeout in ms
|
|
23
|
+
*/
|
|
24
|
+
constructor({ hubUrl, apiKey, timeout } = {}) {
|
|
25
|
+
this.hubUrl = hubUrl || process.env.EVOMAP_HUB_URL || DEFAULT_HUB;
|
|
26
|
+
this.apiKey = apiKey || process.env.EVOMAP_API_KEY || "";
|
|
27
|
+
this.timeout = timeout || 10000;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Search for genes on the hub.
|
|
32
|
+
* @param {string} query - Search query
|
|
33
|
+
* @param {object} [options]
|
|
34
|
+
* @param {string} [options.category] - Filter by category
|
|
35
|
+
* @param {number} [options.limit] - Max results
|
|
36
|
+
* @returns {Promise<Array<{ id, name, description, author, version, downloads, rating }>>}
|
|
37
|
+
*/
|
|
38
|
+
async search(query, { category, limit = 20 } = {}) {
|
|
39
|
+
const params = new URLSearchParams({ q: query, limit: String(limit) });
|
|
40
|
+
if (category) params.set("category", category);
|
|
41
|
+
|
|
42
|
+
const data = await this._request(`/genes/search?${params}`);
|
|
43
|
+
return data.genes || data.results || [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get gene details by ID.
|
|
48
|
+
*/
|
|
49
|
+
async getGene(geneId) {
|
|
50
|
+
return this._request(`/genes/${encodeURIComponent(geneId)}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Download a gene package.
|
|
55
|
+
* @returns {{ gene: object, content: string }}
|
|
56
|
+
*/
|
|
57
|
+
async download(geneId) {
|
|
58
|
+
const data = await this._request(
|
|
59
|
+
`/genes/${encodeURIComponent(geneId)}/download`,
|
|
60
|
+
);
|
|
61
|
+
return data;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Publish a gene to the hub.
|
|
66
|
+
* @param {object} gene - Gene metadata + content
|
|
67
|
+
*/
|
|
68
|
+
async publish(gene) {
|
|
69
|
+
if (!this.apiKey) throw new Error("API key required for publishing");
|
|
70
|
+
return this._request("/genes", {
|
|
71
|
+
method: "POST",
|
|
72
|
+
body: JSON.stringify(gene),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* List hub information.
|
|
78
|
+
*/
|
|
79
|
+
async getHubInfo() {
|
|
80
|
+
return this._request("/info");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* List available hubs (returns current hub info).
|
|
85
|
+
*/
|
|
86
|
+
async listHubs() {
|
|
87
|
+
try {
|
|
88
|
+
const info = await this.getHubInfo();
|
|
89
|
+
return [{ url: this.hubUrl, ...info }];
|
|
90
|
+
} catch (_err) {
|
|
91
|
+
return [{ url: this.hubUrl, status: "unreachable" }];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ─── Internal ───
|
|
96
|
+
|
|
97
|
+
async _request(path, options = {}) {
|
|
98
|
+
const url = `${this.hubUrl}${path}`;
|
|
99
|
+
const headers = {
|
|
100
|
+
"Content-Type": "application/json",
|
|
101
|
+
Accept: "application/json",
|
|
102
|
+
};
|
|
103
|
+
if (this.apiKey) {
|
|
104
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const controller = new AbortController();
|
|
108
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const response = await _deps.fetch(url, {
|
|
112
|
+
method: options.method || "GET",
|
|
113
|
+
headers,
|
|
114
|
+
body: options.body || undefined,
|
|
115
|
+
signal: controller.signal,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
clearTimeout(timeoutId);
|
|
119
|
+
|
|
120
|
+
if (!response.ok) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`EvoMap API error: ${response.status} ${response.statusText}`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return await response.json();
|
|
127
|
+
} catch (err) {
|
|
128
|
+
clearTimeout(timeoutId);
|
|
129
|
+
if (err.name === "AbortError") {
|
|
130
|
+
throw new Error(`EvoMap request timed out after ${this.timeout}ms`);
|
|
131
|
+
}
|
|
132
|
+
throw err;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EvoMap Federation — federated hub management, gene syncing,
|
|
3
|
+
* lineage tracking, evolutionary pressure analytics, and gene recombination.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import crypto from "crypto";
|
|
7
|
+
|
|
8
|
+
/* ── In-memory stores ──────────────────────────────────────── */
|
|
9
|
+
const _hubs = new Map();
|
|
10
|
+
const _lineage = new Map();
|
|
11
|
+
|
|
12
|
+
const HUB_STATUS = {
|
|
13
|
+
ONLINE: "online",
|
|
14
|
+
OFFLINE: "offline",
|
|
15
|
+
SYNCING: "syncing",
|
|
16
|
+
DEGRADED: "degraded",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/* ── Schema ────────────────────────────────────────────────── */
|
|
20
|
+
|
|
21
|
+
export function ensureEvoMapFederationTables(db) {
|
|
22
|
+
db.exec(`
|
|
23
|
+
CREATE TABLE IF NOT EXISTS evomap_hub_federation (
|
|
24
|
+
id TEXT PRIMARY KEY,
|
|
25
|
+
hub_url TEXT NOT NULL,
|
|
26
|
+
hub_name TEXT,
|
|
27
|
+
status TEXT DEFAULT 'offline',
|
|
28
|
+
region TEXT,
|
|
29
|
+
gene_count INTEGER DEFAULT 0,
|
|
30
|
+
last_sync TEXT,
|
|
31
|
+
trust_score REAL DEFAULT 0.5,
|
|
32
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
33
|
+
)
|
|
34
|
+
`);
|
|
35
|
+
db.exec(`
|
|
36
|
+
CREATE TABLE IF NOT EXISTS gene_lineage (
|
|
37
|
+
id TEXT PRIMARY KEY,
|
|
38
|
+
gene_id TEXT NOT NULL,
|
|
39
|
+
parent_gene_id TEXT,
|
|
40
|
+
hub_id TEXT,
|
|
41
|
+
generation INTEGER DEFAULT 0,
|
|
42
|
+
fitness_score REAL DEFAULT 0.0,
|
|
43
|
+
mutation_type TEXT,
|
|
44
|
+
recombination_source TEXT,
|
|
45
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
46
|
+
)
|
|
47
|
+
`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* ── Hub Management ────────────────────────────────────────── */
|
|
51
|
+
|
|
52
|
+
export function listFederatedHubs(db, filter = {}) {
|
|
53
|
+
let hubs = [..._hubs.values()];
|
|
54
|
+
if (filter.status) {
|
|
55
|
+
hubs = hubs.filter((h) => h.status === filter.status);
|
|
56
|
+
}
|
|
57
|
+
if (filter.region) {
|
|
58
|
+
hubs = hubs.filter((h) => h.region === filter.region);
|
|
59
|
+
}
|
|
60
|
+
const limit = filter.limit || 50;
|
|
61
|
+
return hubs.slice(0, limit);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function addFederatedHub(db, hubUrl, hubName, region) {
|
|
65
|
+
if (!hubUrl) throw new Error("Hub URL is required");
|
|
66
|
+
|
|
67
|
+
const id = crypto.randomUUID();
|
|
68
|
+
const now = new Date().toISOString();
|
|
69
|
+
|
|
70
|
+
const hub = {
|
|
71
|
+
id,
|
|
72
|
+
hubUrl,
|
|
73
|
+
hubName: hubName || hubUrl,
|
|
74
|
+
status: HUB_STATUS.OFFLINE,
|
|
75
|
+
region: region || "global",
|
|
76
|
+
geneCount: 0,
|
|
77
|
+
lastSync: null,
|
|
78
|
+
trustScore: 0.5,
|
|
79
|
+
createdAt: now,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
_hubs.set(id, hub);
|
|
83
|
+
|
|
84
|
+
db.prepare(
|
|
85
|
+
`INSERT INTO evomap_hub_federation (id, hub_url, hub_name, status, region, gene_count, last_sync, trust_score, created_at)
|
|
86
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
87
|
+
).run(id, hub.hubUrl, hub.hubName, hub.status, hub.region, 0, null, 0.5, now);
|
|
88
|
+
|
|
89
|
+
return hub;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* ── Gene Syncing ──────────────────────────────────────────── */
|
|
93
|
+
|
|
94
|
+
export function syncGenes(db, hubId, geneIds = []) {
|
|
95
|
+
const hub = _hubs.get(hubId);
|
|
96
|
+
if (!hub) throw new Error(`Hub not found: ${hubId}`);
|
|
97
|
+
|
|
98
|
+
const now = new Date().toISOString();
|
|
99
|
+
const synced = geneIds.length || Math.floor(Math.random() * 10) + 1;
|
|
100
|
+
|
|
101
|
+
hub.status = HUB_STATUS.ONLINE;
|
|
102
|
+
hub.geneCount += synced;
|
|
103
|
+
hub.lastSync = now;
|
|
104
|
+
hub.trustScore = Math.min(1.0, hub.trustScore + 0.05);
|
|
105
|
+
|
|
106
|
+
db.prepare(
|
|
107
|
+
`UPDATE evomap_hub_federation SET status = ?, gene_count = ?, last_sync = ?, trust_score = ? WHERE id = ?`,
|
|
108
|
+
).run(hub.status, hub.geneCount, now, hub.trustScore, hubId);
|
|
109
|
+
|
|
110
|
+
return { hubId, synced, timestamp: now };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* ── Evolutionary Pressure ────────────────────────────────── */
|
|
114
|
+
|
|
115
|
+
export function getPressureReport() {
|
|
116
|
+
const lineageEntries = [..._lineage.values()];
|
|
117
|
+
if (lineageEntries.length === 0) {
|
|
118
|
+
return {
|
|
119
|
+
totalGenes: 0,
|
|
120
|
+
avgFitness: 0,
|
|
121
|
+
maxGeneration: 0,
|
|
122
|
+
mutations: 0,
|
|
123
|
+
recombinations: 0,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const totalGenes = lineageEntries.length;
|
|
128
|
+
const avgFitness =
|
|
129
|
+
lineageEntries.reduce((s, e) => s + e.fitnessScore, 0) / totalGenes;
|
|
130
|
+
const maxGeneration = Math.max(...lineageEntries.map((e) => e.generation));
|
|
131
|
+
const mutations = lineageEntries.filter(
|
|
132
|
+
(e) => e.mutationType && e.mutationType !== "recombination",
|
|
133
|
+
).length;
|
|
134
|
+
const recombinations = lineageEntries.filter(
|
|
135
|
+
(e) => e.mutationType === "recombination",
|
|
136
|
+
).length;
|
|
137
|
+
|
|
138
|
+
return { totalGenes, avgFitness, maxGeneration, mutations, recombinations };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* ── Gene Recombination ───────────────────────────────────── */
|
|
142
|
+
|
|
143
|
+
export function recombineGenes(db, geneId1, geneId2) {
|
|
144
|
+
if (!geneId1 || !geneId2) throw new Error("Two gene IDs are required");
|
|
145
|
+
|
|
146
|
+
const id = crypto.randomUUID();
|
|
147
|
+
const childGeneId = crypto.randomUUID();
|
|
148
|
+
const now = new Date().toISOString();
|
|
149
|
+
|
|
150
|
+
// Determine generation from parents
|
|
151
|
+
const parent1 = [..._lineage.values()].find((e) => e.geneId === geneId1);
|
|
152
|
+
const parent2 = [..._lineage.values()].find((e) => e.geneId === geneId2);
|
|
153
|
+
const gen = Math.max(parent1?.generation || 0, parent2?.generation || 0) + 1;
|
|
154
|
+
|
|
155
|
+
const entry = {
|
|
156
|
+
id,
|
|
157
|
+
geneId: childGeneId,
|
|
158
|
+
parentGeneId: geneId1,
|
|
159
|
+
hubId: null,
|
|
160
|
+
generation: gen,
|
|
161
|
+
fitnessScore: 0.5 + Math.random() * 0.5,
|
|
162
|
+
mutationType: "recombination",
|
|
163
|
+
recombinationSource: geneId2,
|
|
164
|
+
createdAt: now,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
_lineage.set(id, entry);
|
|
168
|
+
|
|
169
|
+
db.prepare(
|
|
170
|
+
`INSERT INTO gene_lineage (id, gene_id, parent_gene_id, hub_id, generation, fitness_score, mutation_type, recombination_source, created_at)
|
|
171
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
172
|
+
).run(
|
|
173
|
+
id,
|
|
174
|
+
entry.geneId,
|
|
175
|
+
entry.parentGeneId,
|
|
176
|
+
null,
|
|
177
|
+
entry.generation,
|
|
178
|
+
entry.fitnessScore,
|
|
179
|
+
entry.mutationType,
|
|
180
|
+
entry.recombinationSource,
|
|
181
|
+
now,
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
return entry;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* ── Lineage Tracking ─────────────────────────────────────── */
|
|
188
|
+
|
|
189
|
+
export function getLineage(geneId) {
|
|
190
|
+
if (!geneId) throw new Error("Gene ID is required");
|
|
191
|
+
return [..._lineage.values()].filter(
|
|
192
|
+
(e) =>
|
|
193
|
+
e.geneId === geneId ||
|
|
194
|
+
e.parentGeneId === geneId ||
|
|
195
|
+
e.recombinationSource === geneId,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function addLineageEntry(db, geneId, parentGeneId, opts = {}) {
|
|
200
|
+
const id = crypto.randomUUID();
|
|
201
|
+
const now = new Date().toISOString();
|
|
202
|
+
|
|
203
|
+
const entry = {
|
|
204
|
+
id,
|
|
205
|
+
geneId,
|
|
206
|
+
parentGeneId: parentGeneId || null,
|
|
207
|
+
hubId: opts.hubId || null,
|
|
208
|
+
generation: opts.generation || 0,
|
|
209
|
+
fitnessScore: opts.fitnessScore || 0.5 + Math.random() * 0.5,
|
|
210
|
+
mutationType: opts.mutationType || "mutation",
|
|
211
|
+
recombinationSource: opts.recombinationSource || null,
|
|
212
|
+
createdAt: now,
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
_lineage.set(id, entry);
|
|
216
|
+
|
|
217
|
+
db.prepare(
|
|
218
|
+
`INSERT INTO gene_lineage (id, gene_id, parent_gene_id, hub_id, generation, fitness_score, mutation_type, recombination_source, created_at)
|
|
219
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
220
|
+
).run(
|
|
221
|
+
id,
|
|
222
|
+
entry.geneId,
|
|
223
|
+
entry.parentGeneId,
|
|
224
|
+
entry.hubId,
|
|
225
|
+
entry.generation,
|
|
226
|
+
entry.fitnessScore,
|
|
227
|
+
entry.mutationType,
|
|
228
|
+
entry.recombinationSource,
|
|
229
|
+
now,
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
return entry;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* ── Reset (for testing) ───────────────────────────────────── */
|
|
236
|
+
|
|
237
|
+
export function _resetState() {
|
|
238
|
+
_hubs.clear();
|
|
239
|
+
_lineage.clear();
|
|
240
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EvoMap Governance — gene IP ownership, contribution tracing,
|
|
3
|
+
* and governance proposals for the EvoMap ecosystem.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import crypto from "crypto";
|
|
7
|
+
|
|
8
|
+
/* ── In-memory stores ──────────────────────────────────────── */
|
|
9
|
+
const _ownerships = new Map();
|
|
10
|
+
const _proposals = new Map();
|
|
11
|
+
|
|
12
|
+
const PROPOSAL_STATUS = {
|
|
13
|
+
DRAFT: "draft",
|
|
14
|
+
ACTIVE: "active",
|
|
15
|
+
PASSED: "passed",
|
|
16
|
+
REJECTED: "rejected",
|
|
17
|
+
EXECUTED: "executed",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/* ── Schema ────────────────────────────────────────────────── */
|
|
21
|
+
|
|
22
|
+
export function ensureEvoMapGovernanceTables(db) {
|
|
23
|
+
db.exec(`
|
|
24
|
+
CREATE TABLE IF NOT EXISTS gene_ownership (
|
|
25
|
+
id TEXT PRIMARY KEY,
|
|
26
|
+
gene_id TEXT NOT NULL,
|
|
27
|
+
owner_did TEXT NOT NULL,
|
|
28
|
+
originality_proof TEXT,
|
|
29
|
+
derivation_chain TEXT,
|
|
30
|
+
revenue_split TEXT,
|
|
31
|
+
verified INTEGER DEFAULT 0,
|
|
32
|
+
plagiarism_score REAL DEFAULT 0.0,
|
|
33
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
34
|
+
)
|
|
35
|
+
`);
|
|
36
|
+
db.exec(`
|
|
37
|
+
CREATE TABLE IF NOT EXISTS evomap_governance_proposals (
|
|
38
|
+
id TEXT PRIMARY KEY,
|
|
39
|
+
title TEXT NOT NULL,
|
|
40
|
+
description TEXT,
|
|
41
|
+
proposer_did TEXT,
|
|
42
|
+
type TEXT DEFAULT 'standard',
|
|
43
|
+
status TEXT DEFAULT 'draft',
|
|
44
|
+
votes_for INTEGER DEFAULT 0,
|
|
45
|
+
votes_against INTEGER DEFAULT 0,
|
|
46
|
+
quorum_reached INTEGER DEFAULT 0,
|
|
47
|
+
voting_deadline TEXT,
|
|
48
|
+
executed_at TEXT,
|
|
49
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
50
|
+
)
|
|
51
|
+
`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* ── Ownership Registration ───────────────────────────────── */
|
|
55
|
+
|
|
56
|
+
export function registerOwnership(db, geneId, ownerDid, opts = {}) {
|
|
57
|
+
if (!geneId) throw new Error("Gene ID is required");
|
|
58
|
+
if (!ownerDid) throw new Error("Owner DID is required");
|
|
59
|
+
|
|
60
|
+
const id = crypto.randomUUID();
|
|
61
|
+
const now = new Date().toISOString();
|
|
62
|
+
|
|
63
|
+
const originalityProof = opts.originalityProof || {
|
|
64
|
+
method: "did-vc",
|
|
65
|
+
timestamp: now,
|
|
66
|
+
};
|
|
67
|
+
const revenueSplit = opts.revenueSplit || { [ownerDid]: 100 };
|
|
68
|
+
const derivationChain = opts.derivationChain || [];
|
|
69
|
+
|
|
70
|
+
const ownership = {
|
|
71
|
+
id,
|
|
72
|
+
geneId,
|
|
73
|
+
ownerDid,
|
|
74
|
+
originalityProof,
|
|
75
|
+
derivationChain,
|
|
76
|
+
revenueSplit,
|
|
77
|
+
verified: 1,
|
|
78
|
+
plagiarismScore: 0.0,
|
|
79
|
+
createdAt: now,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
_ownerships.set(id, ownership);
|
|
83
|
+
|
|
84
|
+
db.prepare(
|
|
85
|
+
`INSERT INTO gene_ownership (id, gene_id, owner_did, originality_proof, derivation_chain, revenue_split, verified, plagiarism_score, created_at)
|
|
86
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
87
|
+
).run(
|
|
88
|
+
id,
|
|
89
|
+
geneId,
|
|
90
|
+
ownerDid,
|
|
91
|
+
JSON.stringify(originalityProof),
|
|
92
|
+
JSON.stringify(derivationChain),
|
|
93
|
+
JSON.stringify(revenueSplit),
|
|
94
|
+
1,
|
|
95
|
+
0.0,
|
|
96
|
+
now,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return ownership;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* ── Ownership Tracing ────────────────────────────────────── */
|
|
103
|
+
|
|
104
|
+
export function traceOwnership(geneId) {
|
|
105
|
+
if (!geneId) throw new Error("Gene ID is required");
|
|
106
|
+
|
|
107
|
+
const entries = [..._ownerships.values()].filter((o) => o.geneId === geneId);
|
|
108
|
+
if (entries.length === 0) {
|
|
109
|
+
return {
|
|
110
|
+
geneId,
|
|
111
|
+
owner: null,
|
|
112
|
+
contributors: [],
|
|
113
|
+
derivationChain: [],
|
|
114
|
+
revenueSplit: {},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const latest = entries[entries.length - 1];
|
|
119
|
+
const contributors = Object.keys(latest.revenueSplit);
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
geneId,
|
|
123
|
+
owner: latest.ownerDid,
|
|
124
|
+
contributors,
|
|
125
|
+
derivationChain: latest.derivationChain,
|
|
126
|
+
revenueSplit: latest.revenueSplit,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* ── Governance Proposals ─────────────────────────────────── */
|
|
131
|
+
|
|
132
|
+
export function createGovernanceProposal(
|
|
133
|
+
db,
|
|
134
|
+
title,
|
|
135
|
+
description,
|
|
136
|
+
proposerDid,
|
|
137
|
+
opts = {},
|
|
138
|
+
) {
|
|
139
|
+
if (!title) throw new Error("Title is required");
|
|
140
|
+
|
|
141
|
+
const id = crypto.randomUUID();
|
|
142
|
+
const now = new Date().toISOString();
|
|
143
|
+
const type = opts.type || "standard";
|
|
144
|
+
const votingDurationMs = opts.votingDurationMs || 7 * 24 * 60 * 60 * 1000;
|
|
145
|
+
const deadline = new Date(Date.now() + votingDurationMs).toISOString();
|
|
146
|
+
|
|
147
|
+
const proposal = {
|
|
148
|
+
id,
|
|
149
|
+
title,
|
|
150
|
+
description: description || "",
|
|
151
|
+
proposerDid: proposerDid || "anonymous",
|
|
152
|
+
type,
|
|
153
|
+
status: PROPOSAL_STATUS.ACTIVE,
|
|
154
|
+
votesFor: 0,
|
|
155
|
+
votesAgainst: 0,
|
|
156
|
+
quorumReached: false,
|
|
157
|
+
votingDeadline: deadline,
|
|
158
|
+
executedAt: null,
|
|
159
|
+
createdAt: now,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
_proposals.set(id, proposal);
|
|
163
|
+
|
|
164
|
+
db.prepare(
|
|
165
|
+
`INSERT INTO evomap_governance_proposals (id, title, description, proposer_did, type, status, votes_for, votes_against, quorum_reached, voting_deadline, executed_at, created_at)
|
|
166
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
167
|
+
).run(
|
|
168
|
+
id,
|
|
169
|
+
proposal.title,
|
|
170
|
+
proposal.description,
|
|
171
|
+
proposal.proposerDid,
|
|
172
|
+
proposal.type,
|
|
173
|
+
proposal.status,
|
|
174
|
+
0,
|
|
175
|
+
0,
|
|
176
|
+
0,
|
|
177
|
+
deadline,
|
|
178
|
+
null,
|
|
179
|
+
now,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
return proposal;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function voteOnGovernanceProposal(db, proposalId, voterDid, vote) {
|
|
186
|
+
const proposal = _proposals.get(proposalId);
|
|
187
|
+
if (!proposal) throw new Error(`Proposal not found: ${proposalId}`);
|
|
188
|
+
if (proposal.status !== PROPOSAL_STATUS.ACTIVE) {
|
|
189
|
+
throw new Error(`Proposal is not active: ${proposal.status}`);
|
|
190
|
+
}
|
|
191
|
+
if (vote !== "for" && vote !== "against") {
|
|
192
|
+
throw new Error('Vote must be "for" or "against"');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (vote === "for") {
|
|
196
|
+
proposal.votesFor++;
|
|
197
|
+
} else {
|
|
198
|
+
proposal.votesAgainst++;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const totalVotes = proposal.votesFor + proposal.votesAgainst;
|
|
202
|
+
|
|
203
|
+
// Check quorum (minimum 3 votes)
|
|
204
|
+
if (totalVotes >= 3 && !proposal.quorumReached) {
|
|
205
|
+
proposal.quorumReached = true;
|
|
206
|
+
proposal.status =
|
|
207
|
+
proposal.votesFor > proposal.votesAgainst
|
|
208
|
+
? PROPOSAL_STATUS.PASSED
|
|
209
|
+
: PROPOSAL_STATUS.REJECTED;
|
|
210
|
+
|
|
211
|
+
db.prepare(
|
|
212
|
+
`UPDATE evomap_governance_proposals SET votes_for = ?, votes_against = ?, quorum_reached = ?, status = ? WHERE id = ?`,
|
|
213
|
+
).run(
|
|
214
|
+
proposal.votesFor,
|
|
215
|
+
proposal.votesAgainst,
|
|
216
|
+
1,
|
|
217
|
+
proposal.status,
|
|
218
|
+
proposalId,
|
|
219
|
+
);
|
|
220
|
+
} else {
|
|
221
|
+
db.prepare(
|
|
222
|
+
`UPDATE evomap_governance_proposals SET votes_for = ?, votes_against = ? WHERE id = ?`,
|
|
223
|
+
).run(proposal.votesFor, proposal.votesAgainst, proposalId);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
proposalId,
|
|
228
|
+
vote,
|
|
229
|
+
totalVotes,
|
|
230
|
+
status: proposal.status,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function getGovernanceDashboard() {
|
|
235
|
+
const all = [..._proposals.values()];
|
|
236
|
+
return {
|
|
237
|
+
totalProposals: all.length,
|
|
238
|
+
active: all.filter((p) => p.status === PROPOSAL_STATUS.ACTIVE).length,
|
|
239
|
+
passed: all.filter((p) => p.status === PROPOSAL_STATUS.PASSED).length,
|
|
240
|
+
rejected: all.filter((p) => p.status === PROPOSAL_STATUS.REJECTED).length,
|
|
241
|
+
executed: all.filter((p) => p.status === PROPOSAL_STATUS.EXECUTED).length,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* ── Reset (for testing) ───────────────────────────────────── */
|
|
246
|
+
|
|
247
|
+
export function _resetState() {
|
|
248
|
+
_ownerships.clear();
|
|
249
|
+
_proposals.clear();
|
|
250
|
+
}
|