chainlesschain 0.66.0 → 0.132.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/bin/chainlesschain.js +0 -0
- package/package.json +1 -1
- package/src/commands/a2a.js +380 -0
- package/src/commands/agent-network.js +254 -1
- package/src/commands/audit.js +302 -0
- package/src/commands/automation.js +271 -1
- package/src/commands/bi.js +348 -0
- package/src/commands/codegen.js +224 -0
- package/src/commands/collab.js +341 -0
- package/src/commands/compliance.js +1035 -0
- package/src/commands/cowork.js +221 -0
- package/src/commands/crosschain.js +218 -0
- package/src/commands/dbevo.js +284 -0
- package/src/commands/dev.js +252 -0
- package/src/commands/did.js +358 -0
- package/src/commands/dlp.js +341 -0
- package/src/commands/encrypt.js +341 -0
- package/src/commands/evomap.js +394 -0
- package/src/commands/export.js +256 -1
- package/src/commands/federation.js +283 -0
- package/src/commands/fusion.js +258 -0
- package/src/commands/governance.js +325 -0
- package/src/commands/hardening.js +411 -0
- package/src/commands/hook.js +148 -0
- package/src/commands/import.js +252 -0
- package/src/commands/incentive.js +322 -0
- package/src/commands/inference.js +318 -0
- package/src/commands/infra.js +244 -0
- package/src/commands/instinct.js +260 -0
- package/src/commands/ipfs.js +318 -0
- package/src/commands/kg.js +387 -0
- package/src/commands/llm.js +263 -0
- package/src/commands/lowcode.js +356 -0
- package/src/commands/marketplace.js +256 -0
- package/src/commands/mcp.js +221 -0
- package/src/commands/memory.js +248 -0
- package/src/commands/multimodal.js +296 -0
- package/src/commands/nlprog.js +356 -0
- package/src/commands/note.js +244 -0
- package/src/commands/ops.js +354 -0
- package/src/commands/orchestrate.js +166 -0
- package/src/commands/org.js +277 -0
- package/src/commands/p2p.js +390 -0
- package/src/commands/perception.js +290 -0
- package/src/commands/permmem.js +251 -0
- package/src/commands/plugin-ecosystem.js +273 -0
- package/src/commands/pqc.js +393 -0
- package/src/commands/privacy.js +321 -0
- package/src/commands/quantization.js +351 -0
- package/src/commands/rcache.js +271 -0
- package/src/commands/recommend.js +340 -0
- package/src/commands/reputation.js +261 -0
- package/src/commands/runtime.js +307 -0
- package/src/commands/scim.js +262 -0
- package/src/commands/session.js +258 -0
- package/src/commands/siem.js +246 -0
- package/src/commands/skill.js +267 -1
- package/src/commands/sla.js +259 -0
- package/src/commands/social.js +256 -0
- package/src/commands/sso.js +186 -1
- package/src/commands/stress.js +230 -0
- package/src/commands/sync.js +256 -0
- package/src/commands/tech.js +338 -0
- package/src/commands/tenant.js +351 -0
- package/src/commands/terraform.js +245 -0
- package/src/commands/tokens.js +269 -0
- package/src/commands/trust.js +249 -0
- package/src/commands/wallet.js +277 -0
- package/src/commands/workflow.js +171 -0
- package/src/commands/zkp.js +335 -0
- package/src/index.js +4 -0
- package/src/lib/a2a-protocol.js +451 -0
- package/src/lib/agent-coordinator.js +325 -0
- package/src/lib/agent-network.js +387 -0
- package/src/lib/agent-router.js +395 -0
- package/src/lib/aiops.js +478 -0
- package/src/lib/app-builder.js +239 -0
- package/src/lib/audit-logger.js +379 -0
- package/src/lib/automation-engine.js +330 -0
- package/src/lib/autonomous-developer.js +350 -0
- package/src/lib/bi-engine.js +338 -0
- package/src/lib/code-agent.js +323 -0
- package/src/lib/collaboration-governance.js +364 -0
- package/src/lib/community-governance.js +436 -0
- package/src/lib/compliance-manager.js +434 -0
- package/src/lib/content-recommendation.js +469 -0
- package/src/lib/cross-chain.js +345 -0
- package/src/lib/crypto-manager.js +350 -0
- package/src/lib/dbevo.js +338 -0
- package/src/lib/decentral-infra.js +340 -0
- package/src/lib/did-manager.js +367 -0
- package/src/lib/dlp-engine.js +389 -0
- package/src/lib/evomap-federation.js +177 -0
- package/src/lib/evomap-governance.js +276 -0
- package/src/lib/federation-hardening.js +259 -0
- package/src/lib/hardening-manager.js +348 -0
- package/src/lib/hook-manager.js +380 -0
- package/src/lib/inference-network.js +330 -0
- package/src/lib/instinct-manager.js +332 -0
- package/src/lib/ipfs-storage.js +334 -0
- package/src/lib/knowledge-exporter.js +381 -0
- package/src/lib/knowledge-graph.js +432 -0
- package/src/lib/knowledge-importer.js +379 -0
- package/src/lib/llm-providers.js +391 -0
- package/src/lib/mcp-registry.js +333 -0
- package/src/lib/memory-manager.js +330 -0
- package/src/lib/multimodal.js +346 -0
- package/src/lib/nl-programming.js +343 -0
- package/src/lib/note-versioning.js +327 -0
- package/src/lib/org-manager.js +323 -0
- package/src/lib/p2p-manager.js +387 -0
- package/src/lib/perception.js +346 -0
- package/src/lib/perf-tuning.js +4 -1
- package/src/lib/permanent-memory.js +320 -0
- package/src/lib/plugin-ecosystem.js +377 -0
- package/src/lib/pqc-manager.js +368 -0
- package/src/lib/privacy-computing.js +427 -0
- package/src/lib/protocol-fusion.js +417 -0
- package/src/lib/quantization.js +325 -0
- package/src/lib/reputation-optimizer.js +299 -0
- package/src/lib/response-cache.js +327 -0
- package/src/lib/scim-manager.js +329 -0
- package/src/lib/session-manager.js +329 -0
- package/src/lib/siem-exporter.js +333 -0
- package/src/lib/skill-loader.js +377 -0
- package/src/lib/skill-marketplace.js +325 -0
- package/src/lib/sla-manager.js +275 -0
- package/src/lib/social-manager.js +326 -0
- package/src/lib/sso-manager.js +332 -0
- package/src/lib/stress-tester.js +330 -0
- package/src/lib/sync-manager.js +326 -0
- package/src/lib/tech-learning-engine.js +369 -0
- package/src/lib/tenant-saas.js +460 -0
- package/src/lib/terraform-manager.js +363 -0
- package/src/lib/threat-intel.js +335 -0
- package/src/lib/token-incentive.js +293 -0
- package/src/lib/token-tracker.js +329 -0
- package/src/lib/trust-security.js +390 -0
- package/src/lib/ueba.js +389 -0
- package/src/lib/universal-runtime.js +325 -0
- package/src/lib/wallet-manager.js +326 -0
- package/src/lib/workflow-engine.js +322 -0
- package/src/lib/zkp-engine.js +274 -0
package/src/lib/app-builder.js
CHANGED
|
@@ -503,3 +503,242 @@ export function deployApp(db, appId, options = {}) {
|
|
|
503
503
|
const deployedAt = new Date().toISOString();
|
|
504
504
|
return { appId, outputDir, files: fileNames, deployedAt };
|
|
505
505
|
}
|
|
506
|
+
|
|
507
|
+
// ─── Phase 93 V2 surface (strictly additive) ───────────────────────────
|
|
508
|
+
|
|
509
|
+
export const COMPONENT_CATEGORY = Object.freeze({
|
|
510
|
+
INPUT: "input",
|
|
511
|
+
DISPLAY: "display",
|
|
512
|
+
CHART: "chart",
|
|
513
|
+
LAYOUT: "layout",
|
|
514
|
+
OVERLAY: "overlay",
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
export const DATASOURCE_TYPE = Object.freeze({
|
|
518
|
+
REST: "rest",
|
|
519
|
+
GRAPHQL: "graphql",
|
|
520
|
+
DATABASE: "database",
|
|
521
|
+
CSV: "csv",
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
export const APP_STATUS = Object.freeze({
|
|
525
|
+
DRAFT: "draft",
|
|
526
|
+
PUBLISHED: "published",
|
|
527
|
+
ARCHIVED: "archived",
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
const _allowedStatusTransitions = Object.freeze({
|
|
531
|
+
draft: new Set(["published", "archived"]),
|
|
532
|
+
published: new Set(["draft", "archived"]),
|
|
533
|
+
archived: new Set(["draft"]),
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
const _v2DataSources = new Map();
|
|
537
|
+
const _v2StatusHistory = new Map();
|
|
538
|
+
|
|
539
|
+
function _isValidStatusTransition(from, to) {
|
|
540
|
+
if (from === to) return true;
|
|
541
|
+
const allowed = _allowedStatusTransitions[from];
|
|
542
|
+
return !!(allowed && allowed.has(to));
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
export function listComponentsV2({ category } = {}) {
|
|
546
|
+
const all = listComponents();
|
|
547
|
+
if (!category) return all.map((c) => ({ ...c }));
|
|
548
|
+
const valid = Object.values(COMPONENT_CATEGORY);
|
|
549
|
+
if (!valid.includes(category)) {
|
|
550
|
+
throw new Error(
|
|
551
|
+
`Invalid category '${category}'. Expected one of ${valid.join(", ")}`,
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
return all.filter((c) => c.category === category).map((c) => ({ ...c }));
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export function registerDataSourceV2(db, { appId, name, type, config = {} }) {
|
|
558
|
+
const valid = Object.values(DATASOURCE_TYPE);
|
|
559
|
+
if (!valid.includes(type)) {
|
|
560
|
+
throw new Error(
|
|
561
|
+
`Invalid datasource type '${type}'. Expected one of ${valid.join(", ")}`,
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
if (!appId) throw new Error("appId is required");
|
|
565
|
+
if (!name) throw new Error("name is required");
|
|
566
|
+
|
|
567
|
+
const result = addDataSource(db, appId, name, type, config);
|
|
568
|
+
_v2DataSources.set(result.id, { ...result, config, validated: false });
|
|
569
|
+
return result;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export function testDataSourceConnection(dataSourceId) {
|
|
573
|
+
const ds = _v2DataSources.get(dataSourceId);
|
|
574
|
+
if (!ds) {
|
|
575
|
+
return { dataSourceId, ok: false, reason: "datasource not found" };
|
|
576
|
+
}
|
|
577
|
+
const config = ds.config || {};
|
|
578
|
+
let ok = false;
|
|
579
|
+
let reason = "";
|
|
580
|
+
switch (ds.type) {
|
|
581
|
+
case DATASOURCE_TYPE.REST:
|
|
582
|
+
ok = typeof config.url === "string" && config.url.length > 0;
|
|
583
|
+
reason = ok ? "ok" : "missing url";
|
|
584
|
+
break;
|
|
585
|
+
case DATASOURCE_TYPE.GRAPHQL:
|
|
586
|
+
ok = typeof config.endpoint === "string" && config.endpoint.length > 0;
|
|
587
|
+
reason = ok ? "ok" : "missing endpoint";
|
|
588
|
+
break;
|
|
589
|
+
case DATASOURCE_TYPE.DATABASE:
|
|
590
|
+
ok = typeof config.host === "string" && config.host.length > 0;
|
|
591
|
+
reason = ok ? "ok" : "missing host";
|
|
592
|
+
break;
|
|
593
|
+
case DATASOURCE_TYPE.CSV:
|
|
594
|
+
ok = typeof config.path === "string" && config.path.length > 0;
|
|
595
|
+
reason = ok ? "ok" : "missing path";
|
|
596
|
+
break;
|
|
597
|
+
default:
|
|
598
|
+
reason = "unknown type";
|
|
599
|
+
}
|
|
600
|
+
if (ok) {
|
|
601
|
+
ds.validated = true;
|
|
602
|
+
_v2DataSources.set(dataSourceId, ds);
|
|
603
|
+
}
|
|
604
|
+
return { dataSourceId, type: ds.type, ok, reason };
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
export function updateAppStatus(db, { appId, status }) {
|
|
608
|
+
const valid = Object.values(APP_STATUS);
|
|
609
|
+
if (!valid.includes(status)) {
|
|
610
|
+
throw new Error(
|
|
611
|
+
`Invalid status '${status}'. Expected one of ${valid.join(", ")}`,
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
const app = getApp(db, appId);
|
|
615
|
+
if (!app) throw new Error(`App '${appId}' not found`);
|
|
616
|
+
|
|
617
|
+
const currentStatus =
|
|
618
|
+
app.status === "deployed" ? APP_STATUS.PUBLISHED : app.status;
|
|
619
|
+
if (!_isValidStatusTransition(currentStatus, status)) {
|
|
620
|
+
throw new Error(`Invalid status transition: ${currentStatus} → ${status}`);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
db.prepare(
|
|
624
|
+
`UPDATE lowcode_apps SET status = ?, updated_at = datetime('now') WHERE id = ?`,
|
|
625
|
+
).run(status, appId);
|
|
626
|
+
if (_apps.has(appId)) _apps.get(appId).status = status;
|
|
627
|
+
|
|
628
|
+
const hist = _v2StatusHistory.get(appId) || [];
|
|
629
|
+
hist.push({
|
|
630
|
+
from: currentStatus,
|
|
631
|
+
to: status,
|
|
632
|
+
at: new Date().toISOString(),
|
|
633
|
+
});
|
|
634
|
+
_v2StatusHistory.set(appId, hist);
|
|
635
|
+
return { appId, status, previous: currentStatus };
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
export function archiveApp(db, appId) {
|
|
639
|
+
return updateAppStatus(db, { appId, status: APP_STATUS.ARCHIVED });
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
export function getStatusHistory(appId) {
|
|
643
|
+
return (_v2StatusHistory.get(appId) || []).slice();
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
export function cloneApp(db, { sourceId, newName }) {
|
|
647
|
+
const source = getApp(db, sourceId);
|
|
648
|
+
if (!source) throw new Error(`App '${sourceId}' not found`);
|
|
649
|
+
const cloned = createApp(db, {
|
|
650
|
+
name: newName || `${source.name} (Copy)`,
|
|
651
|
+
description: source.description,
|
|
652
|
+
platform: source.platform,
|
|
653
|
+
design: source.design,
|
|
654
|
+
});
|
|
655
|
+
// Persist the copied design so version snapshot matches source
|
|
656
|
+
if (
|
|
657
|
+
source.design &&
|
|
658
|
+
source.design.components &&
|
|
659
|
+
source.design.components.length > 0
|
|
660
|
+
) {
|
|
661
|
+
saveDesign(db, cloned.id, source.design);
|
|
662
|
+
}
|
|
663
|
+
return { sourceId, clonedId: cloned.id, name: cloned.name };
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
export function exportAppJSON(db, appId) {
|
|
667
|
+
const app = getApp(db, appId);
|
|
668
|
+
if (!app) throw new Error(`App '${appId}' not found`);
|
|
669
|
+
const legacy = exportApp(appId);
|
|
670
|
+
return {
|
|
671
|
+
schema: "chainlesschain.lowcode.v2",
|
|
672
|
+
exportedAt: new Date().toISOString(),
|
|
673
|
+
app,
|
|
674
|
+
dataSources: legacy.dataSources,
|
|
675
|
+
versions: legacy.versions,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
export function importAppJSON(db, json) {
|
|
680
|
+
if (!json || typeof json !== "object") {
|
|
681
|
+
throw new Error("import payload must be a JSON object");
|
|
682
|
+
}
|
|
683
|
+
if (json.schema !== "chainlesschain.lowcode.v2") {
|
|
684
|
+
throw new Error(
|
|
685
|
+
`unsupported schema '${json.schema}'. Expected 'chainlesschain.lowcode.v2'`,
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
if (!json.app || !json.app.name) {
|
|
689
|
+
throw new Error("import payload missing app.name");
|
|
690
|
+
}
|
|
691
|
+
const imported = createApp(db, {
|
|
692
|
+
name: json.app.name,
|
|
693
|
+
description: json.app.description || "",
|
|
694
|
+
platform: json.app.platform || "web",
|
|
695
|
+
design: json.app.design || { components: [], layout: {} },
|
|
696
|
+
});
|
|
697
|
+
let dsCount = 0;
|
|
698
|
+
for (const ds of json.dataSources || []) {
|
|
699
|
+
if (!ds.type || !ds.name) continue;
|
|
700
|
+
if (!Object.values(DATASOURCE_TYPE).includes(ds.type)) continue;
|
|
701
|
+
registerDataSourceV2(db, {
|
|
702
|
+
appId: imported.id,
|
|
703
|
+
name: ds.name,
|
|
704
|
+
type: ds.type,
|
|
705
|
+
config: ds.config || {},
|
|
706
|
+
});
|
|
707
|
+
dsCount++;
|
|
708
|
+
}
|
|
709
|
+
return { importedId: imported.id, name: imported.name, dataSources: dsCount };
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
export function getLowcodeStatsV2(db) {
|
|
713
|
+
const rows = db.prepare(`SELECT status, platform FROM lowcode_apps`).all();
|
|
714
|
+
const byStatus = { draft: 0, published: 0, archived: 0, deployed: 0 };
|
|
715
|
+
const byPlatform = {};
|
|
716
|
+
for (const r of rows) {
|
|
717
|
+
const s = r.status || "draft";
|
|
718
|
+
byStatus[s] = (byStatus[s] || 0) + 1;
|
|
719
|
+
const p = r.platform || "web";
|
|
720
|
+
byPlatform[p] = (byPlatform[p] || 0) + 1;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const dsRows = db.prepare(`SELECT type FROM lowcode_datasources`).all();
|
|
724
|
+
const byDataSourceType = {};
|
|
725
|
+
for (const r of dsRows) {
|
|
726
|
+
byDataSourceType[r.type] = (byDataSourceType[r.type] || 0) + 1;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
return {
|
|
730
|
+
totalApps: rows.length,
|
|
731
|
+
byStatus,
|
|
732
|
+
byPlatform,
|
|
733
|
+
dataSources: {
|
|
734
|
+
total: dsRows.length,
|
|
735
|
+
byType: byDataSourceType,
|
|
736
|
+
},
|
|
737
|
+
componentsAvailable: listComponents().length,
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
export function _resetV2State() {
|
|
742
|
+
_v2DataSources.clear();
|
|
743
|
+
_v2StatusHistory.clear();
|
|
744
|
+
}
|
package/src/lib/audit-logger.js
CHANGED
|
@@ -362,3 +362,382 @@ export function purgeLogs(db, daysToKeep = 90) {
|
|
|
362
362
|
export function getRecentEvents(db, limit = 20) {
|
|
363
363
|
return queryLogs(db, { limit });
|
|
364
364
|
}
|
|
365
|
+
|
|
366
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
367
|
+
V2 SURFACE (Phase 11 canonical) — strictly additive
|
|
368
|
+
═══════════════════════════════════════════════════════════════ */
|
|
369
|
+
|
|
370
|
+
export const LOG_STATUS_V2 = Object.freeze({
|
|
371
|
+
ACTIVE: "active",
|
|
372
|
+
ARCHIVED: "archived",
|
|
373
|
+
PURGED: "purged",
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
export const INTEGRITY_STATUS_V2 = Object.freeze({
|
|
377
|
+
UNVERIFIED: "unverified",
|
|
378
|
+
VERIFIED: "verified",
|
|
379
|
+
CORRUPTED: "corrupted",
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
export const ALERT_STATUS_V2 = Object.freeze({
|
|
383
|
+
OPEN: "open",
|
|
384
|
+
ACKNOWLEDGED: "acknowledged",
|
|
385
|
+
RESOLVED: "resolved",
|
|
386
|
+
DISMISSED: "dismissed",
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
export const EVENT_TYPES_V2 = Object.freeze([
|
|
390
|
+
"auth",
|
|
391
|
+
"permission",
|
|
392
|
+
"data",
|
|
393
|
+
"system",
|
|
394
|
+
"file",
|
|
395
|
+
"did",
|
|
396
|
+
"crypto",
|
|
397
|
+
"api",
|
|
398
|
+
]);
|
|
399
|
+
|
|
400
|
+
export const RISK_LEVELS_V2 = Object.freeze([
|
|
401
|
+
"low",
|
|
402
|
+
"medium",
|
|
403
|
+
"high",
|
|
404
|
+
"critical",
|
|
405
|
+
]);
|
|
406
|
+
|
|
407
|
+
export const AUDIT_DEFAULT_MAX_ALERTS_PER_ACTOR = 10;
|
|
408
|
+
export const AUDIT_DEFAULT_ARCHIVE_RETENTION_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
409
|
+
export const AUDIT_DEFAULT_PURGE_RETENTION_MS = 365 * 24 * 60 * 60 * 1000; // 365 days
|
|
410
|
+
|
|
411
|
+
let _maxAlertsPerActor = AUDIT_DEFAULT_MAX_ALERTS_PER_ACTOR;
|
|
412
|
+
let _archiveRetentionMs = AUDIT_DEFAULT_ARCHIVE_RETENTION_MS;
|
|
413
|
+
let _purgeRetentionMs = AUDIT_DEFAULT_PURGE_RETENTION_MS;
|
|
414
|
+
|
|
415
|
+
const _logStatesV2 = new Map();
|
|
416
|
+
const _alertStatesV2 = new Map();
|
|
417
|
+
let _lastChainHash = null;
|
|
418
|
+
|
|
419
|
+
const LOG_TRANSITIONS_V2 = new Map([
|
|
420
|
+
["active", new Set(["archived", "purged"])],
|
|
421
|
+
["archived", new Set(["purged"])],
|
|
422
|
+
]);
|
|
423
|
+
const LOG_TERMINALS_V2 = new Set(["purged"]);
|
|
424
|
+
|
|
425
|
+
const ALERT_TRANSITIONS_V2 = new Map([
|
|
426
|
+
["open", new Set(["acknowledged", "dismissed"])],
|
|
427
|
+
["acknowledged", new Set(["resolved", "dismissed"])],
|
|
428
|
+
]);
|
|
429
|
+
const ALERT_TERMINALS_V2 = new Set(["resolved", "dismissed"]);
|
|
430
|
+
|
|
431
|
+
function _positiveInt(n, label) {
|
|
432
|
+
const v = Number(n);
|
|
433
|
+
if (!Number.isFinite(v) || v <= 0) {
|
|
434
|
+
throw new Error(`${label} must be a positive integer`);
|
|
435
|
+
}
|
|
436
|
+
return Math.floor(v);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export function setMaxAlertsPerActor(n) {
|
|
440
|
+
_maxAlertsPerActor = _positiveInt(n, "maxAlertsPerActor");
|
|
441
|
+
return _maxAlertsPerActor;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export function setArchiveRetentionMs(ms) {
|
|
445
|
+
_archiveRetentionMs = _positiveInt(ms, "archiveRetentionMs");
|
|
446
|
+
return _archiveRetentionMs;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export function setPurgeRetentionMs(ms) {
|
|
450
|
+
_purgeRetentionMs = _positiveInt(ms, "purgeRetentionMs");
|
|
451
|
+
return _purgeRetentionMs;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export function getMaxAlertsPerActor() {
|
|
455
|
+
return _maxAlertsPerActor;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export function getArchiveRetentionMs() {
|
|
459
|
+
return _archiveRetentionMs;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export function getPurgeRetentionMs() {
|
|
463
|
+
return _purgeRetentionMs;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export function getOpenAlertCount(actor) {
|
|
467
|
+
let count = 0;
|
|
468
|
+
for (const entry of _alertStatesV2.values()) {
|
|
469
|
+
if (entry.status === ALERT_STATUS_V2.OPEN) {
|
|
470
|
+
if (!actor || entry.actor === actor) count += 1;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return count;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function _hashChainEntry(prevHash, entry) {
|
|
477
|
+
const payload = JSON.stringify({
|
|
478
|
+
logId: entry.logId,
|
|
479
|
+
eventType: entry.eventType,
|
|
480
|
+
operation: entry.operation,
|
|
481
|
+
actor: entry.actor,
|
|
482
|
+
riskLevel: entry.riskLevel,
|
|
483
|
+
createdAt: entry.createdAt,
|
|
484
|
+
prev: prevHash || "",
|
|
485
|
+
});
|
|
486
|
+
return crypto.createHash("sha256").update(payload).digest("hex");
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/* ── Log V2 (hash-chained) ──────────────────────────────────── */
|
|
490
|
+
|
|
491
|
+
export function logEventV2(
|
|
492
|
+
db,
|
|
493
|
+
{
|
|
494
|
+
logId,
|
|
495
|
+
eventType,
|
|
496
|
+
operation,
|
|
497
|
+
actor,
|
|
498
|
+
target,
|
|
499
|
+
details,
|
|
500
|
+
riskLevel,
|
|
501
|
+
ipAddress,
|
|
502
|
+
userAgent,
|
|
503
|
+
success,
|
|
504
|
+
errorMessage,
|
|
505
|
+
} = {},
|
|
506
|
+
) {
|
|
507
|
+
if (!logId) throw new Error("logId is required");
|
|
508
|
+
if (!operation) throw new Error("operation is required");
|
|
509
|
+
if (!eventType) throw new Error("eventType is required");
|
|
510
|
+
if (!EVENT_TYPES_V2.includes(eventType)) {
|
|
511
|
+
throw new Error(`Invalid eventType: ${eventType}`);
|
|
512
|
+
}
|
|
513
|
+
if (_logStatesV2.has(logId)) {
|
|
514
|
+
throw new Error(`Log already registered: ${logId}`);
|
|
515
|
+
}
|
|
516
|
+
const risk = riskLevel || assessRisk(eventType, operation, details);
|
|
517
|
+
if (!RISK_LEVELS_V2.includes(risk)) {
|
|
518
|
+
throw new Error(`Invalid riskLevel: ${risk}`);
|
|
519
|
+
}
|
|
520
|
+
const now = Date.now();
|
|
521
|
+
const entry = {
|
|
522
|
+
logId,
|
|
523
|
+
eventType,
|
|
524
|
+
operation,
|
|
525
|
+
actor: actor || null,
|
|
526
|
+
target: target || null,
|
|
527
|
+
details: sanitizeDetails(details) || null,
|
|
528
|
+
riskLevel: risk,
|
|
529
|
+
ipAddress: ipAddress || null,
|
|
530
|
+
userAgent: userAgent || null,
|
|
531
|
+
success: success !== false,
|
|
532
|
+
errorMessage: errorMessage || null,
|
|
533
|
+
status: LOG_STATUS_V2.ACTIVE,
|
|
534
|
+
integrityStatus: INTEGRITY_STATUS_V2.UNVERIFIED,
|
|
535
|
+
prevHash: _lastChainHash,
|
|
536
|
+
hash: null,
|
|
537
|
+
createdAt: now,
|
|
538
|
+
updatedAt: now,
|
|
539
|
+
};
|
|
540
|
+
entry.hash = _hashChainEntry(_lastChainHash, entry);
|
|
541
|
+
_lastChainHash = entry.hash;
|
|
542
|
+
_logStatesV2.set(logId, entry);
|
|
543
|
+
|
|
544
|
+
// Auto-create alert on critical events
|
|
545
|
+
if (risk === "critical" && actor) {
|
|
546
|
+
const openCount = getOpenAlertCount(actor);
|
|
547
|
+
if (openCount < _maxAlertsPerActor) {
|
|
548
|
+
const alertId = `alert-${logId}`;
|
|
549
|
+
_alertStatesV2.set(alertId, {
|
|
550
|
+
alertId,
|
|
551
|
+
logId,
|
|
552
|
+
actor,
|
|
553
|
+
operation,
|
|
554
|
+
riskLevel: risk,
|
|
555
|
+
status: ALERT_STATUS_V2.OPEN,
|
|
556
|
+
reason: null,
|
|
557
|
+
metadata: {},
|
|
558
|
+
createdAt: now,
|
|
559
|
+
updatedAt: now,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return { ...entry };
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export function getLogStatusV2(logId) {
|
|
568
|
+
const entry = _logStatesV2.get(logId);
|
|
569
|
+
return entry ? { ...entry } : null;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export function setLogStatusV2(db, logId, newStatus, patch = {}) {
|
|
573
|
+
const entry = _logStatesV2.get(logId);
|
|
574
|
+
if (!entry) throw new Error(`Log not found: ${logId}`);
|
|
575
|
+
if (!Object.values(LOG_STATUS_V2).includes(newStatus)) {
|
|
576
|
+
throw new Error(`Invalid log status: ${newStatus}`);
|
|
577
|
+
}
|
|
578
|
+
if (LOG_TERMINALS_V2.has(entry.status)) {
|
|
579
|
+
throw new Error(`Log is terminal: ${entry.status}`);
|
|
580
|
+
}
|
|
581
|
+
const allowed = LOG_TRANSITIONS_V2.get(entry.status) || new Set();
|
|
582
|
+
if (!allowed.has(newStatus)) {
|
|
583
|
+
throw new Error(`Invalid transition: ${entry.status} → ${newStatus}`);
|
|
584
|
+
}
|
|
585
|
+
entry.status = newStatus;
|
|
586
|
+
entry.updatedAt = Date.now();
|
|
587
|
+
if (patch.reason !== undefined) entry.reason = patch.reason;
|
|
588
|
+
return { ...entry };
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export function verifyChainV2() {
|
|
592
|
+
const results = [];
|
|
593
|
+
let prevHash = null;
|
|
594
|
+
const entries = [..._logStatesV2.values()].sort(
|
|
595
|
+
(a, b) => a.createdAt - b.createdAt,
|
|
596
|
+
);
|
|
597
|
+
for (const entry of entries) {
|
|
598
|
+
const expected = _hashChainEntry(prevHash, entry);
|
|
599
|
+
const valid = expected === entry.hash && entry.prevHash === prevHash;
|
|
600
|
+
entry.integrityStatus = valid
|
|
601
|
+
? INTEGRITY_STATUS_V2.VERIFIED
|
|
602
|
+
: INTEGRITY_STATUS_V2.CORRUPTED;
|
|
603
|
+
results.push({
|
|
604
|
+
logId: entry.logId,
|
|
605
|
+
valid,
|
|
606
|
+
integrityStatus: entry.integrityStatus,
|
|
607
|
+
});
|
|
608
|
+
prevHash = entry.hash;
|
|
609
|
+
}
|
|
610
|
+
return results;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export function autoArchiveLogs(db, nowMs = Date.now()) {
|
|
614
|
+
const archived = [];
|
|
615
|
+
for (const entry of _logStatesV2.values()) {
|
|
616
|
+
if (entry.status !== LOG_STATUS_V2.ACTIVE) continue;
|
|
617
|
+
if (nowMs - entry.createdAt > _archiveRetentionMs) {
|
|
618
|
+
entry.status = LOG_STATUS_V2.ARCHIVED;
|
|
619
|
+
entry.updatedAt = nowMs;
|
|
620
|
+
archived.push({ ...entry });
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return archived;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export function autoPurgeLogs(db, nowMs = Date.now()) {
|
|
627
|
+
const purged = [];
|
|
628
|
+
for (const entry of _logStatesV2.values()) {
|
|
629
|
+
if (entry.status !== LOG_STATUS_V2.ARCHIVED) continue;
|
|
630
|
+
if (nowMs - entry.createdAt > _purgeRetentionMs) {
|
|
631
|
+
entry.status = LOG_STATUS_V2.PURGED;
|
|
632
|
+
entry.updatedAt = nowMs;
|
|
633
|
+
purged.push({ ...entry });
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return purged;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/* ── Alert V2 ───────────────────────────────────────────────── */
|
|
640
|
+
|
|
641
|
+
export function getAlertStatusV2(alertId) {
|
|
642
|
+
const entry = _alertStatesV2.get(alertId);
|
|
643
|
+
return entry ? { ...entry } : null;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
export function setAlertStatusV2(db, alertId, newStatus, patch = {}) {
|
|
647
|
+
const entry = _alertStatesV2.get(alertId);
|
|
648
|
+
if (!entry) throw new Error(`Alert not found: ${alertId}`);
|
|
649
|
+
if (!Object.values(ALERT_STATUS_V2).includes(newStatus)) {
|
|
650
|
+
throw new Error(`Invalid alert status: ${newStatus}`);
|
|
651
|
+
}
|
|
652
|
+
if (ALERT_TERMINALS_V2.has(entry.status)) {
|
|
653
|
+
throw new Error(`Alert is terminal: ${entry.status}`);
|
|
654
|
+
}
|
|
655
|
+
const allowed = ALERT_TRANSITIONS_V2.get(entry.status) || new Set();
|
|
656
|
+
if (!allowed.has(newStatus)) {
|
|
657
|
+
throw new Error(`Invalid transition: ${entry.status} → ${newStatus}`);
|
|
658
|
+
}
|
|
659
|
+
entry.status = newStatus;
|
|
660
|
+
entry.updatedAt = Date.now();
|
|
661
|
+
if (patch.reason !== undefined) entry.reason = patch.reason;
|
|
662
|
+
if (patch.metadata) entry.metadata = { ...entry.metadata, ...patch.metadata };
|
|
663
|
+
return { ...entry };
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
export function acknowledgeAlert(db, alertId, reason) {
|
|
667
|
+
return setAlertStatusV2(db, alertId, ALERT_STATUS_V2.ACKNOWLEDGED, {
|
|
668
|
+
reason,
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
export function resolveAlert(db, alertId, reason) {
|
|
673
|
+
return setAlertStatusV2(db, alertId, ALERT_STATUS_V2.RESOLVED, { reason });
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
export function dismissAlert(db, alertId, reason) {
|
|
677
|
+
return setAlertStatusV2(db, alertId, ALERT_STATUS_V2.DISMISSED, { reason });
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/* ── Stats V2 ───────────────────────────────────────────────── */
|
|
681
|
+
|
|
682
|
+
export function getAuditStatsV2() {
|
|
683
|
+
const logsByStatus = { active: 0, archived: 0, purged: 0 };
|
|
684
|
+
const logsByRisk = { low: 0, medium: 0, high: 0, critical: 0 };
|
|
685
|
+
const logsByIntegrity = { unverified: 0, verified: 0, corrupted: 0 };
|
|
686
|
+
const logsByEventType = {
|
|
687
|
+
auth: 0,
|
|
688
|
+
permission: 0,
|
|
689
|
+
data: 0,
|
|
690
|
+
system: 0,
|
|
691
|
+
file: 0,
|
|
692
|
+
did: 0,
|
|
693
|
+
crypto: 0,
|
|
694
|
+
api: 0,
|
|
695
|
+
};
|
|
696
|
+
const alertsByStatus = {
|
|
697
|
+
open: 0,
|
|
698
|
+
acknowledged: 0,
|
|
699
|
+
resolved: 0,
|
|
700
|
+
dismissed: 0,
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
for (const entry of _logStatesV2.values()) {
|
|
704
|
+
if (logsByStatus[entry.status] !== undefined)
|
|
705
|
+
logsByStatus[entry.status] += 1;
|
|
706
|
+
if (logsByRisk[entry.riskLevel] !== undefined)
|
|
707
|
+
logsByRisk[entry.riskLevel] += 1;
|
|
708
|
+
if (logsByIntegrity[entry.integrityStatus] !== undefined)
|
|
709
|
+
logsByIntegrity[entry.integrityStatus] += 1;
|
|
710
|
+
if (logsByEventType[entry.eventType] !== undefined)
|
|
711
|
+
logsByEventType[entry.eventType] += 1;
|
|
712
|
+
}
|
|
713
|
+
for (const entry of _alertStatesV2.values()) {
|
|
714
|
+
if (alertsByStatus[entry.status] !== undefined)
|
|
715
|
+
alertsByStatus[entry.status] += 1;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
return {
|
|
719
|
+
totalLogs: _logStatesV2.size,
|
|
720
|
+
totalAlerts: _alertStatesV2.size,
|
|
721
|
+
activeAlerts: alertsByStatus.open,
|
|
722
|
+
maxAlertsPerActor: _maxAlertsPerActor,
|
|
723
|
+
archiveRetentionMs: _archiveRetentionMs,
|
|
724
|
+
purgeRetentionMs: _purgeRetentionMs,
|
|
725
|
+
lastChainHash: _lastChainHash,
|
|
726
|
+
logsByStatus,
|
|
727
|
+
logsByRisk,
|
|
728
|
+
logsByIntegrity,
|
|
729
|
+
logsByEventType,
|
|
730
|
+
alertsByStatus,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/* ── Reset (for testing) ───────────────────────────────────── */
|
|
735
|
+
|
|
736
|
+
export function _resetStateV2() {
|
|
737
|
+
_logStatesV2.clear();
|
|
738
|
+
_alertStatesV2.clear();
|
|
739
|
+
_lastChainHash = null;
|
|
740
|
+
_maxAlertsPerActor = AUDIT_DEFAULT_MAX_ALERTS_PER_ACTOR;
|
|
741
|
+
_archiveRetentionMs = AUDIT_DEFAULT_ARCHIVE_RETENTION_MS;
|
|
742
|
+
_purgeRetentionMs = AUDIT_DEFAULT_PURGE_RETENTION_MS;
|
|
743
|
+
}
|