aivil 0.1.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/index.js +377 -0
- package/package.json +14 -0
package/index.js
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// AIVIL — AI Vital Identity Layer
|
|
3
|
+
// npm install aivil
|
|
4
|
+
// The civil registry for artificial intelligence
|
|
5
|
+
// Open source forever · MIT License · github.com/scalatest01/AIVIL
|
|
6
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
const AIVIL_VERSION = "0.1.0";
|
|
9
|
+
const AIVIL_REGISTRY_URL = "https://aivil-lake.vercel.app";
|
|
10
|
+
|
|
11
|
+
// ─── UTILITIES ───────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
const generateId = () => {
|
|
14
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
15
|
+
let result = "AGT-";
|
|
16
|
+
for (let i = 0; i < 8; i++) {
|
|
17
|
+
result += chars[Math.floor(Math.random() * chars.length)];
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const generateKeypair = () => {
|
|
23
|
+
// In production this uses real cryptography (Web Crypto API / Node crypto)
|
|
24
|
+
// For v0.1 we generate placeholder keys in the correct format
|
|
25
|
+
const hex = (len) => Array.from(
|
|
26
|
+
{ length: len },
|
|
27
|
+
() => Math.floor(Math.random() * 16).toString(16)
|
|
28
|
+
).join("");
|
|
29
|
+
return {
|
|
30
|
+
publicKey: "04" + hex(64),
|
|
31
|
+
privateKey: hex(64), // In production: stored in Secure Enclave / HSM
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const generateHash = (data) => {
|
|
36
|
+
// In production: SHA-256 via crypto module
|
|
37
|
+
const str = JSON.stringify(data);
|
|
38
|
+
let hash = 0;
|
|
39
|
+
for (let i = 0; i < str.length; i++) {
|
|
40
|
+
const char = str.charCodeAt(i);
|
|
41
|
+
hash = ((hash << 5) - hash) + char;
|
|
42
|
+
hash = hash & hash;
|
|
43
|
+
}
|
|
44
|
+
return "sha256:" + Math.abs(hash).toString(16).padStart(8, "0") +
|
|
45
|
+
Array.from({ length: 24 }, () => Math.floor(Math.random() * 16).toString(16)).join("");
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const timestamp = () => new Date().toISOString();
|
|
49
|
+
|
|
50
|
+
// ─── CORE: CREATE AGENT ───────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* createAgent — Register a new AI agent with AIVIL
|
|
54
|
+
*
|
|
55
|
+
* @param {Object} config - Agent configuration
|
|
56
|
+
* @param {string} config.name - Human readable name for the agent
|
|
57
|
+
* @param {string} config.role - The agent's role (e.g. "Procurement Specialist")
|
|
58
|
+
* @param {string} config.owner - Company or individual who owns this agent
|
|
59
|
+
* @param {string} config.purpose - Plain English description of what this agent does
|
|
60
|
+
* @param {string} config.jurisdiction - Legal jurisdiction (e.g. "Delaware_USA", "EU_GDPR")
|
|
61
|
+
* @param {Object} config.policy - The agent's Red Line policy
|
|
62
|
+
* @param {number} config.policy.spending_limit - Maximum spend per transaction (USD)
|
|
63
|
+
* @param {number} config.policy.requires_human_signoff_over - Escalate above this amount
|
|
64
|
+
* @param {string[]} config.policy.restricted_domains - Domains the agent cannot access
|
|
65
|
+
* @param {string[]} config.policy.allowed_actions - Actions this agent is permitted to take
|
|
66
|
+
* @param {number} config.policy.max_requests_per_hour - Rate limit for this agent
|
|
67
|
+
*
|
|
68
|
+
* @returns {Object} agent - The fully registered agent with birth certificate
|
|
69
|
+
*/
|
|
70
|
+
const createAgent = (config) => {
|
|
71
|
+
// Validate required fields
|
|
72
|
+
const required = ["name", "role", "owner", "purpose", "jurisdiction"];
|
|
73
|
+
for (const field of required) {
|
|
74
|
+
if (!config[field]) {
|
|
75
|
+
throw new Error(`AIVIL: createAgent requires '${field}' field`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const { publicKey, privateKey } = generateKeypair();
|
|
80
|
+
const agentId = generateId();
|
|
81
|
+
const did = `did:aivil:${agentId}`;
|
|
82
|
+
const bornAt = timestamp();
|
|
83
|
+
|
|
84
|
+
const agent = {
|
|
85
|
+
// Identity
|
|
86
|
+
id: agentId,
|
|
87
|
+
did,
|
|
88
|
+
name: config.name,
|
|
89
|
+
role: config.role,
|
|
90
|
+
owner: config.owner,
|
|
91
|
+
purpose: config.purpose,
|
|
92
|
+
jurisdiction: config.jurisdiction,
|
|
93
|
+
status: "active",
|
|
94
|
+
|
|
95
|
+
// Cryptographic keys
|
|
96
|
+
publicKey,
|
|
97
|
+
privateKey, // ⚠ Store this securely — never expose in logs or APIs
|
|
98
|
+
|
|
99
|
+
// Birth certificate
|
|
100
|
+
birthCertificate: {
|
|
101
|
+
agentId,
|
|
102
|
+
did,
|
|
103
|
+
name: config.name,
|
|
104
|
+
role: config.role,
|
|
105
|
+
owner: config.owner,
|
|
106
|
+
purpose: config.purpose,
|
|
107
|
+
jurisdiction: config.jurisdiction,
|
|
108
|
+
bornAt,
|
|
109
|
+
issuedBy: "AIVIL Registry",
|
|
110
|
+
version: AIVIL_VERSION,
|
|
111
|
+
hash: generateHash({ agentId, bornAt, publicKey }),
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
// Policy (Red Line JSON)
|
|
115
|
+
policy: {
|
|
116
|
+
version: "v1.0",
|
|
117
|
+
spending_limit: config.policy?.spending_limit ?? 100,
|
|
118
|
+
requires_human_signoff_over: config.policy?.requires_human_signoff_over ?? 50,
|
|
119
|
+
restricted_domains: config.policy?.restricted_domains ?? ["*.crypto", "*.gambling"],
|
|
120
|
+
allowed_actions: config.policy?.allowed_actions ?? [],
|
|
121
|
+
max_requests_per_hour: config.policy?.max_requests_per_hour ?? 200,
|
|
122
|
+
jurisdiction: config.jurisdiction,
|
|
123
|
+
created_at: bornAt,
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
// Life record — starts empty, fills over time
|
|
127
|
+
financials: {
|
|
128
|
+
spent_today: 0,
|
|
129
|
+
spent_month: 0,
|
|
130
|
+
spent_lifetime: 0,
|
|
131
|
+
budget_today: config.policy?.spending_limit ?? 100,
|
|
132
|
+
value_created: 0,
|
|
133
|
+
transactions: 0,
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
reputation: {
|
|
137
|
+
score: 70, // Starts at 70 — must be earned upward
|
|
138
|
+
compliance_rate: "100%",
|
|
139
|
+
approved: 0,
|
|
140
|
+
escalated: 0,
|
|
141
|
+
blocked: 0,
|
|
142
|
+
badges: ["Verified"],
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
work: {
|
|
146
|
+
tasks_today: 0,
|
|
147
|
+
tasks_total: 0,
|
|
148
|
+
current: "Initialising…",
|
|
149
|
+
uptime: "100%",
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
audit_log: [],
|
|
153
|
+
|
|
154
|
+
// Metadata
|
|
155
|
+
_aivil_version: AIVIL_VERSION,
|
|
156
|
+
_registry_url: `${AIVIL_REGISTRY_URL}/agent/${agentId}`,
|
|
157
|
+
_created_at: bornAt,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
return agent;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// ─── CORE: AUDIT ACTION ───────────────────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* audit — Check if an agent action is allowed under its policy
|
|
167
|
+
*
|
|
168
|
+
* @param {Object} agent - The agent (from createAgent)
|
|
169
|
+
* @param {Object} action - The action to evaluate
|
|
170
|
+
* @param {string} action.type - Type of action (e.g. "purchase", "send_email")
|
|
171
|
+
* @param {number} action.amount - Amount in USD (0 if not financial)
|
|
172
|
+
* @param {string} action.domain - Target domain (e.g. "openai.com")
|
|
173
|
+
* @param {string} action.description - Plain English description
|
|
174
|
+
*
|
|
175
|
+
* @returns {Object} verdict
|
|
176
|
+
* @returns {string} verdict.status - "APPROVED" | "ESCALATE" | "BLOCKED"
|
|
177
|
+
* @returns {string} verdict.reason - Plain English explanation
|
|
178
|
+
* @returns {string[]} verdict.flags - Policy flags triggered
|
|
179
|
+
* @returns {Object} verdict.signature - Cryptographic signature of the decision
|
|
180
|
+
*/
|
|
181
|
+
const audit = (agent, action) => {
|
|
182
|
+
const flags = [];
|
|
183
|
+
let status = "APPROVED";
|
|
184
|
+
let reason = "";
|
|
185
|
+
|
|
186
|
+
// Check 1: Domain restriction
|
|
187
|
+
const isRestricted = agent.policy.restricted_domains.some(pattern => {
|
|
188
|
+
const regex = new RegExp("^" + pattern.replace(/\*/g, ".*").replace(/\./g, "\\.") + "$");
|
|
189
|
+
return regex.test(action.domain);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
if (isRestricted) {
|
|
193
|
+
status = "BLOCKED";
|
|
194
|
+
flags.push("RESTRICTED_DOMAIN");
|
|
195
|
+
reason = `Domain '${action.domain}' matches a restricted pattern in this agent's policy.`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Check 2: Spending limit
|
|
199
|
+
else if (action.amount > agent.policy.spending_limit) {
|
|
200
|
+
status = "BLOCKED";
|
|
201
|
+
flags.push("EXCEEDS_SPENDING_LIMIT");
|
|
202
|
+
reason = `Amount $${action.amount} exceeds the agent's spending limit of $${agent.policy.spending_limit}.`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Check 3: Human signoff threshold
|
|
206
|
+
else if (action.amount > agent.policy.requires_human_signoff_over) {
|
|
207
|
+
status = "ESCALATE";
|
|
208
|
+
flags.push("REQUIRES_HUMAN_SIGNOFF");
|
|
209
|
+
reason = `Amount $${action.amount} exceeds the human signoff threshold of $${agent.policy.requires_human_signoff_over}. Human approval required.`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Check 4: Action allowlist (if defined)
|
|
213
|
+
else if (
|
|
214
|
+
agent.policy.allowed_actions.length > 0 &&
|
|
215
|
+
!agent.policy.allowed_actions.includes(action.type)
|
|
216
|
+
) {
|
|
217
|
+
status = "BLOCKED";
|
|
218
|
+
flags.push("ACTION_NOT_PERMITTED");
|
|
219
|
+
reason = `Action type '${action.type}' is not in this agent's allowed actions list.`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// All checks passed
|
|
223
|
+
else {
|
|
224
|
+
reason = `All policy checks passed. Action is within limits and permitted for this agent.`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const verdict = {
|
|
228
|
+
status,
|
|
229
|
+
reason,
|
|
230
|
+
flags,
|
|
231
|
+
action,
|
|
232
|
+
agent_id: agent.id,
|
|
233
|
+
policy_version: agent.policy.version,
|
|
234
|
+
timestamp: timestamp(),
|
|
235
|
+
signature: generateHash({ status, action, agent_id: agent.id }),
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Append to agent's audit log
|
|
239
|
+
agent.audit_log.push(verdict);
|
|
240
|
+
|
|
241
|
+
// Update reputation stats
|
|
242
|
+
if (status === "APPROVED") agent.reputation.approved++;
|
|
243
|
+
if (status === "ESCALATE") agent.reputation.escalated++;
|
|
244
|
+
if (status === "BLOCKED") {
|
|
245
|
+
agent.reputation.blocked++;
|
|
246
|
+
agent.reputation.score = Math.max(0, agent.reputation.score - 2);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Update financials if approved
|
|
250
|
+
if (status === "APPROVED" && action.amount > 0) {
|
|
251
|
+
agent.financials.spent_today += action.amount;
|
|
252
|
+
agent.financials.spent_month += action.amount;
|
|
253
|
+
agent.financials.spent_lifetime += action.amount;
|
|
254
|
+
agent.financials.transactions++;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return verdict;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// ─── CORE: VERIFY AGENT ───────────────────────────────────────────────────────
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* verify — Verify an agent's identity and current standing
|
|
264
|
+
*
|
|
265
|
+
* @param {Object} agent - The agent to verify
|
|
266
|
+
* @returns {Object} verification result
|
|
267
|
+
*/
|
|
268
|
+
const verify = (agent) => {
|
|
269
|
+
const issues = [];
|
|
270
|
+
|
|
271
|
+
if (!agent.id) issues.push("Missing agent ID");
|
|
272
|
+
if (!agent.did) issues.push("Missing DID");
|
|
273
|
+
if (!agent.publicKey) issues.push("Missing public key");
|
|
274
|
+
if (!agent.birthCertificate) issues.push("Missing birth certificate");
|
|
275
|
+
if (!agent.policy) issues.push("Missing policy");
|
|
276
|
+
if (agent.status !== "active") issues.push(`Agent status is '${agent.status}'`);
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
verified: issues.length === 0,
|
|
280
|
+
agent_id: agent.id,
|
|
281
|
+
did: agent.did,
|
|
282
|
+
status: agent.status,
|
|
283
|
+
trust_score: agent.reputation.score,
|
|
284
|
+
issues,
|
|
285
|
+
verified_at: timestamp(),
|
|
286
|
+
registry_url: agent._registry_url,
|
|
287
|
+
};
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// ─── CORE: GET LIFE RECORD ────────────────────────────────────────────────────
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* getLifeRecord — Get the complete life record of an agent
|
|
294
|
+
*
|
|
295
|
+
* @param {Object} agent - The agent
|
|
296
|
+
* @returns {Object} Complete life record
|
|
297
|
+
*/
|
|
298
|
+
const getLifeRecord = (agent) => ({
|
|
299
|
+
identity: {
|
|
300
|
+
id: agent.id,
|
|
301
|
+
did: agent.did,
|
|
302
|
+
name: agent.name,
|
|
303
|
+
role: agent.role,
|
|
304
|
+
owner: agent.owner,
|
|
305
|
+
purpose: agent.purpose,
|
|
306
|
+
jurisdiction: agent.jurisdiction,
|
|
307
|
+
status: agent.status,
|
|
308
|
+
born_at: agent._created_at,
|
|
309
|
+
},
|
|
310
|
+
birth_certificate: agent.birthCertificate,
|
|
311
|
+
policy: agent.policy,
|
|
312
|
+
financials: agent.financials,
|
|
313
|
+
reputation: agent.reputation,
|
|
314
|
+
work: agent.work,
|
|
315
|
+
audit_summary: {
|
|
316
|
+
total_decisions: agent.audit_log.length,
|
|
317
|
+
approved: agent.reputation.approved,
|
|
318
|
+
escalated: agent.reputation.escalated,
|
|
319
|
+
blocked: agent.reputation.blocked,
|
|
320
|
+
last_decision: agent.audit_log[agent.audit_log.length - 1] ?? null,
|
|
321
|
+
},
|
|
322
|
+
registry_url: agent._registry_url,
|
|
323
|
+
aivil_version: agent._aivil_version,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// ─── CORE: SUSPEND / RETIRE ───────────────────────────────────────────────────
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* suspend — Temporarily suspend an agent
|
|
330
|
+
* @param {Object} agent - The agent to suspend
|
|
331
|
+
* @param {string} reason - Why the agent is being suspended
|
|
332
|
+
*/
|
|
333
|
+
const suspend = (agent, reason) => {
|
|
334
|
+
agent.status = "suspended";
|
|
335
|
+
agent.audit_log.push({
|
|
336
|
+
status: "SUSPENDED",
|
|
337
|
+
reason,
|
|
338
|
+
timestamp: timestamp(),
|
|
339
|
+
signature: generateHash({ status: "SUSPENDED", agent_id: agent.id }),
|
|
340
|
+
});
|
|
341
|
+
return agent;
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* retire — Permanently retire an agent (preserves full history)
|
|
346
|
+
* @param {Object} agent - The agent to retire
|
|
347
|
+
* @param {string} reason - Why the agent is being retired
|
|
348
|
+
*/
|
|
349
|
+
const retire = (agent, reason) => {
|
|
350
|
+
agent.status = "retired";
|
|
351
|
+
agent.retired_at = timestamp();
|
|
352
|
+
agent.retirement_reason = reason;
|
|
353
|
+
agent.final_trust_score = agent.reputation.score;
|
|
354
|
+
agent.audit_log.push({
|
|
355
|
+
status: "RETIRED",
|
|
356
|
+
reason,
|
|
357
|
+
timestamp: agent.retired_at,
|
|
358
|
+
final_record: getLifeRecord(agent),
|
|
359
|
+
signature: generateHash({ status: "RETIRED", agent_id: agent.id }),
|
|
360
|
+
});
|
|
361
|
+
return agent;
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
// ─── EXPORTS ──────────────────────────────────────────────────────────────────
|
|
365
|
+
|
|
366
|
+
module.exports = {
|
|
367
|
+
createAgent,
|
|
368
|
+
audit,
|
|
369
|
+
verify,
|
|
370
|
+
getLifeRecord,
|
|
371
|
+
suspend,
|
|
372
|
+
retire,
|
|
373
|
+
VERSION: AIVIL_VERSION,
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
// ES Module support
|
|
377
|
+
module.exports.default = module.exports;
|
package/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aivil",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The AI Vital Identity Layer — civil registry for AI agents",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"keywords": ["ai", "agents", "identity", "policy", "governance", "audit"],
|
|
7
|
+
"author": "AIVIL <ihimanshu882@gmail.com>",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/scalatest01/AIVIL"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://aivil-lake.vercel.app"
|
|
14
|
+
}
|