chainlesschain 0.51.0 → 0.81.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.
Files changed (70) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/{AppLayout-Rvi759IS.js → AppLayout-6SPt_8Y_.js} +1 -1
  4. package/src/assets/web-panel/assets/{Dashboard-DBhFxXYQ.js → Dashboard-Br7kCwKJ.js} +2 -2
  5. package/src/assets/web-panel/assets/Dashboard-CKeMmCoT.css +1 -0
  6. package/src/assets/web-panel/assets/{index-uL0cZ8N_.js → index-tN-8TosE.js} +2 -2
  7. package/src/assets/web-panel/index.html +2 -2
  8. package/src/commands/a2a.js +380 -0
  9. package/src/commands/agent-network.js +785 -0
  10. package/src/commands/automation.js +654 -0
  11. package/src/commands/bi.js +348 -0
  12. package/src/commands/crosschain.js +218 -0
  13. package/src/commands/dao.js +565 -0
  14. package/src/commands/did-v2.js +620 -0
  15. package/src/commands/dlp.js +341 -0
  16. package/src/commands/economy.js +578 -0
  17. package/src/commands/evolution.js +391 -0
  18. package/src/commands/evomap.js +394 -0
  19. package/src/commands/federation.js +283 -0
  20. package/src/commands/hmemory.js +442 -0
  21. package/src/commands/inference.js +318 -0
  22. package/src/commands/lowcode.js +356 -0
  23. package/src/commands/marketplace.js +256 -0
  24. package/src/commands/perf.js +433 -0
  25. package/src/commands/pipeline.js +449 -0
  26. package/src/commands/plugin-ecosystem.js +517 -0
  27. package/src/commands/privacy.js +321 -0
  28. package/src/commands/reputation.js +261 -0
  29. package/src/commands/sandbox.js +401 -0
  30. package/src/commands/siem.js +246 -0
  31. package/src/commands/sla.js +259 -0
  32. package/src/commands/social.js +311 -0
  33. package/src/commands/sso.js +798 -0
  34. package/src/commands/stress.js +230 -0
  35. package/src/commands/terraform.js +245 -0
  36. package/src/commands/workflow.js +320 -0
  37. package/src/commands/zkp.js +562 -1
  38. package/src/index.js +21 -0
  39. package/src/lib/a2a-protocol.js +451 -0
  40. package/src/lib/agent-economy.js +479 -0
  41. package/src/lib/agent-network.js +1121 -0
  42. package/src/lib/app-builder.js +239 -0
  43. package/src/lib/automation-engine.js +948 -0
  44. package/src/lib/bi-engine.js +338 -0
  45. package/src/lib/cross-chain.js +345 -0
  46. package/src/lib/dao-governance.js +569 -0
  47. package/src/lib/did-v2-manager.js +1127 -0
  48. package/src/lib/dlp-engine.js +389 -0
  49. package/src/lib/evolution-system.js +453 -0
  50. package/src/lib/evomap-federation.js +177 -0
  51. package/src/lib/evomap-governance.js +276 -0
  52. package/src/lib/federation-hardening.js +259 -0
  53. package/src/lib/hierarchical-memory.js +481 -0
  54. package/src/lib/inference-network.js +330 -0
  55. package/src/lib/perf-tuning.js +734 -0
  56. package/src/lib/pipeline-orchestrator.js +928 -0
  57. package/src/lib/plugin-ecosystem.js +1109 -0
  58. package/src/lib/privacy-computing.js +427 -0
  59. package/src/lib/reputation-optimizer.js +299 -0
  60. package/src/lib/sandbox-v2.js +306 -0
  61. package/src/lib/siem-exporter.js +333 -0
  62. package/src/lib/skill-marketplace.js +325 -0
  63. package/src/lib/sla-manager.js +275 -0
  64. package/src/lib/social-graph-analytics.js +707 -0
  65. package/src/lib/sso-manager.js +841 -0
  66. package/src/lib/stress-tester.js +330 -0
  67. package/src/lib/terraform-manager.js +363 -0
  68. package/src/lib/workflow-engine.js +454 -1
  69. package/src/lib/zkp-engine.js +523 -20
  70. package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +0 -1
@@ -506,4 +506,303 @@ export function _resetState() {
506
506
  _runs.clear();
507
507
  _analytics.clear();
508
508
  _seq = 0;
509
+ _maxConcurrentOptimizations = DEFAULT_MAX_CONCURRENT_OPTIMIZATIONS;
510
+ }
511
+
512
+ /* ═══════════════════════════════════════════════════════════════
513
+ * V2 (Phase 60) — Frozen enums + async optimization lifecycle +
514
+ * concurrency limiter + patch-merged setRunStatus + stats-v2.
515
+ * Strictly additive on top of the legacy surface above.
516
+ * ═══════════════════════════════════════════════════════════════ */
517
+
518
+ export const RUN_STATUS_V2 = Object.freeze({
519
+ RUNNING: "running",
520
+ COMPLETE: "complete",
521
+ APPLIED: "applied",
522
+ FAILED: "failed",
523
+ CANCELLED: "cancelled",
524
+ });
525
+
526
+ export const OBJECTIVE_V2 = Object.freeze({
527
+ ACCURACY: "accuracy",
528
+ FAIRNESS: "fairness",
529
+ RESILIENCE: "resilience",
530
+ CONVERGENCE_SPEED: "convergence_speed",
531
+ });
532
+
533
+ export const DECAY_MODEL_V2 = Object.freeze({
534
+ EXPONENTIAL: "exponential",
535
+ LINEAR: "linear",
536
+ STEP: "step",
537
+ NONE: "none",
538
+ });
539
+
540
+ export const ANOMALY_METHOD_V2 = Object.freeze({
541
+ IQR: "iqr",
542
+ Z_SCORE: "z_score",
543
+ });
544
+
545
+ const DEFAULT_MAX_CONCURRENT_OPTIMIZATIONS = 2;
546
+ let _maxConcurrentOptimizations = DEFAULT_MAX_CONCURRENT_OPTIMIZATIONS;
547
+ export const REPUTATION_DEFAULT_MAX_CONCURRENT =
548
+ DEFAULT_MAX_CONCURRENT_OPTIMIZATIONS;
549
+
550
+ export function setMaxConcurrentOptimizations(n) {
551
+ if (typeof n !== "number" || !Number.isFinite(n) || n < 1) {
552
+ throw new Error("maxConcurrentOptimizations must be a positive integer");
553
+ }
554
+ _maxConcurrentOptimizations = Math.floor(n);
555
+ return _maxConcurrentOptimizations;
556
+ }
557
+
558
+ export function getMaxConcurrentOptimizations() {
559
+ return _maxConcurrentOptimizations;
560
+ }
561
+
562
+ // Run state machine:
563
+ // running → { complete, failed, cancelled }
564
+ // complete → { applied }
565
+ // applied/failed/cancelled are terminal.
566
+ const _runTerminal = new Set([
567
+ RUN_STATUS_V2.APPLIED,
568
+ RUN_STATUS_V2.FAILED,
569
+ RUN_STATUS_V2.CANCELLED,
570
+ ]);
571
+ const _runAllowed = new Map([
572
+ [
573
+ RUN_STATUS_V2.RUNNING,
574
+ new Set([
575
+ RUN_STATUS_V2.COMPLETE,
576
+ RUN_STATUS_V2.FAILED,
577
+ RUN_STATUS_V2.CANCELLED,
578
+ ]),
579
+ ],
580
+ [RUN_STATUS_V2.COMPLETE, new Set([RUN_STATUS_V2.APPLIED])],
581
+ [RUN_STATUS_V2.APPLIED, new Set([])],
582
+ [RUN_STATUS_V2.FAILED, new Set([])],
583
+ [RUN_STATUS_V2.CANCELLED, new Set([])],
584
+ ]);
585
+
586
+ export function getActiveOptimizationCount() {
587
+ let count = 0;
588
+ for (const r of _runs.values()) {
589
+ if (r.status === RUN_STATUS_V2.RUNNING) count++;
590
+ }
591
+ return count;
592
+ }
593
+
594
+ /**
595
+ * startOptimizationV2 — creates a RUNNING row without computing iterations.
596
+ * Caller drives the transition via completeOptimization or
597
+ * cancelOptimization / failOptimization, or the generic setRunStatus.
598
+ */
599
+ export function startOptimizationV2(db, opts = {}) {
600
+ const objective = opts.objective || OBJECTIVE_V2.ACCURACY;
601
+ if (!Object.values(OBJECTIVE_V2).includes(objective)) {
602
+ throw new Error(`Unknown objective: ${objective}`);
603
+ }
604
+ const rawIter = opts.iterations == null ? 50 : Number(opts.iterations);
605
+ const iterations = Math.max(
606
+ 1,
607
+ Math.min(1000, Number.isFinite(rawIter) ? rawIter : 50),
608
+ );
609
+
610
+ const activeCount = getActiveOptimizationCount();
611
+ if (activeCount >= _maxConcurrentOptimizations) {
612
+ throw new Error(
613
+ `Max concurrent optimizations reached: ${activeCount}/${_maxConcurrentOptimizations}`,
614
+ );
615
+ }
616
+
617
+ const runId = crypto.randomUUID();
618
+ const now = Date.now();
619
+ const paramSpace = {
620
+ lambda: [0.01, 0.5],
621
+ kappa: [0.5, 3.0],
622
+ contamination: [0.01, 0.2],
623
+ };
624
+
625
+ const run = {
626
+ runId,
627
+ objective,
628
+ iterations,
629
+ paramSpace,
630
+ bestParams: null,
631
+ bestScore: null,
632
+ errorMessage: null,
633
+ status: RUN_STATUS_V2.RUNNING,
634
+ createdAt: now,
635
+ completedAt: null,
636
+ _seq: ++_seq,
637
+ };
638
+ _runs.set(runId, run);
639
+
640
+ db.prepare(
641
+ `INSERT INTO reputation_optimization_runs (run_id, objective, iterations, param_space, best_params, best_score, status, created_at, completed_at)
642
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
643
+ ).run(
644
+ runId,
645
+ objective,
646
+ iterations,
647
+ JSON.stringify(paramSpace),
648
+ null,
649
+ null,
650
+ run.status,
651
+ now,
652
+ null,
653
+ );
654
+
655
+ const { _seq: _omit, ...rest } = run;
656
+ void _omit;
657
+ return rest;
658
+ }
659
+
660
+ /**
661
+ * completeOptimization — advances RUNNING → COMPLETE, runs the iteration
662
+ * loop, writes analytics, and returns the same { ...run, analytics }
663
+ * shape as legacy startOptimization.
664
+ */
665
+ export function completeOptimization(db, runId) {
666
+ const run = _runs.get(runId);
667
+ if (!run) throw new Error(`Optimization run not found: ${runId}`);
668
+
669
+ const allowed = _runAllowed.get(run.status);
670
+ if (!allowed || !allowed.has(RUN_STATUS_V2.COMPLETE)) {
671
+ throw new Error(`Invalid run status transition: ${run.status} → complete`);
672
+ }
673
+
674
+ let bestParams = null;
675
+ let bestScore = -Infinity;
676
+ const history = [];
677
+
678
+ for (let i = 0; i < run.iterations; i++) {
679
+ const params = _sampleParams(run._seq * 101 + i * 17 + 1);
680
+ const score = _evaluateParams(params, run.objective, i);
681
+ history.push({ iteration: i, params, score });
682
+ if (score > bestScore) {
683
+ bestScore = score;
684
+ bestParams = params;
685
+ }
686
+ }
687
+
688
+ const now = Date.now();
689
+ run.status = RUN_STATUS_V2.COMPLETE;
690
+ run.bestParams = bestParams;
691
+ run.bestScore = Number(bestScore.toFixed(6));
692
+ run.completedAt = now;
693
+ run.history = history;
694
+
695
+ db.prepare(
696
+ `UPDATE reputation_optimization_runs SET status = ?, best_params = ?, best_score = ?, completed_at = ? WHERE run_id = ?`,
697
+ ).run(run.status, JSON.stringify(bestParams), run.bestScore, now, runId);
698
+
699
+ const distribution = _buildDistribution(listScores({ decay: "none" }));
700
+ const anomalies = detectAnomalies({ method: "z_score" });
701
+ const recommendations = _buildRecommendations(run.objective, bestParams);
702
+ const analyticsId = crypto.randomUUID();
703
+ const analytics = {
704
+ analyticsId,
705
+ runId,
706
+ reputationDistribution: distribution,
707
+ anomalies,
708
+ recommendations,
709
+ createdAt: now,
710
+ };
711
+ _analytics.set(runId, analytics);
712
+ db.prepare(
713
+ `INSERT INTO reputation_analytics (analytics_id, run_id, reputation_distribution, anomalies, recommendations, created_at)
714
+ VALUES (?, ?, ?, ?, ?, ?)`,
715
+ ).run(
716
+ analyticsId,
717
+ runId,
718
+ JSON.stringify(distribution),
719
+ JSON.stringify(anomalies),
720
+ JSON.stringify(recommendations),
721
+ now,
722
+ );
723
+
724
+ const { _seq: _omit, ...rest } = run;
725
+ void _omit;
726
+ return { ...rest, analytics };
727
+ }
728
+
729
+ export function cancelOptimization(db, runId) {
730
+ return setRunStatus(db, runId, RUN_STATUS_V2.CANCELLED);
731
+ }
732
+
733
+ export function failOptimization(db, runId, errorMessage) {
734
+ return setRunStatus(db, runId, RUN_STATUS_V2.FAILED, { errorMessage });
735
+ }
736
+
737
+ export function applyOptimization(db, runId) {
738
+ return setRunStatus(db, runId, RUN_STATUS_V2.APPLIED);
739
+ }
740
+
741
+ export function setRunStatus(db, runId, newStatus, patch = {}) {
742
+ const run = _runs.get(runId);
743
+ if (!run) throw new Error(`Optimization run not found: ${runId}`);
744
+
745
+ if (!Object.values(RUN_STATUS_V2).includes(newStatus)) {
746
+ throw new Error(`Unknown run status: ${newStatus}`);
747
+ }
748
+
749
+ const allowed = _runAllowed.get(run.status);
750
+ if (!allowed || !allowed.has(newStatus)) {
751
+ throw new Error(
752
+ `Invalid run status transition: ${run.status} → ${newStatus}`,
753
+ );
754
+ }
755
+
756
+ run.status = newStatus;
757
+ if (typeof patch.errorMessage === "string") {
758
+ run.errorMessage = patch.errorMessage;
759
+ }
760
+ if (_runTerminal.has(newStatus) && run.completedAt == null) {
761
+ run.completedAt = Date.now();
762
+ }
763
+
764
+ db.prepare(
765
+ `UPDATE reputation_optimization_runs SET status = ?, completed_at = ? WHERE run_id = ?`,
766
+ ).run(newStatus, run.completedAt, runId);
767
+
768
+ const { _seq: _omit, ...rest } = run;
769
+ void _omit;
770
+ return rest;
771
+ }
772
+
773
+ export function getReputationStatsV2() {
774
+ const runs = [..._runs.values()];
775
+ const observations = [];
776
+ for (const arr of _observations.values()) observations.push(...arr);
777
+
778
+ const byStatus = {};
779
+ for (const s of Object.values(RUN_STATUS_V2)) byStatus[s] = 0;
780
+ for (const r of runs) byStatus[r.status] = (byStatus[r.status] || 0) + 1;
781
+
782
+ const byObjective = {};
783
+ for (const o of Object.values(OBJECTIVE_V2)) byObjective[o] = 0;
784
+ for (const r of runs)
785
+ byObjective[r.objective] = (byObjective[r.objective] || 0) + 1;
786
+
787
+ let totalObservations = observations.length;
788
+ let totalDids = _observations.size;
789
+ let bestScore = null;
790
+ for (const r of runs) {
791
+ if (r.bestScore != null && (bestScore == null || r.bestScore > bestScore)) {
792
+ bestScore = r.bestScore;
793
+ }
794
+ }
795
+
796
+ return {
797
+ totalRuns: runs.length,
798
+ activeRuns: getActiveOptimizationCount(),
799
+ maxConcurrentOptimizations: _maxConcurrentOptimizations,
800
+ byStatus,
801
+ byObjective,
802
+ observations: {
803
+ totalDids,
804
+ totalObservations,
805
+ },
806
+ bestScoreEver: bestScore,
807
+ };
509
808
  }
@@ -695,4 +695,310 @@ export function pruneExpired(db, now = Date.now()) {
695
695
  export function _resetState() {
696
696
  activeSandboxes.clear();
697
697
  auditLog.length = 0;
698
+ _v2Isolations.length = 0;
699
+ }
700
+
701
+ // ═════════════════════════════════════════════════════════════════
702
+ // Phase 87 — Agent Security Sandbox 2.0 additions (strictly-additive)
703
+ // Frozen canonical enums + lifecycle state machine (pause/resume/
704
+ // terminate) + per-type quota + explicit enforce/check helpers +
705
+ // risk-level classification + auto-isolate + filtered audit/stats.
706
+ // ═════════════════════════════════════════════════════════════════
707
+
708
+ export const SANDBOX_STATUS = Object.freeze({
709
+ CREATING: "creating",
710
+ READY: "ready",
711
+ RUNNING: "running",
712
+ PAUSED: "paused",
713
+ TERMINATED: "terminated",
714
+ ERROR: "error",
715
+ });
716
+
717
+ export const PERMISSION_TYPE = Object.freeze({
718
+ FILESYSTEM: "filesystem",
719
+ NETWORK: "network",
720
+ SYSCALL: "syscall",
721
+ IPC: "ipc",
722
+ PROCESS: "process",
723
+ });
724
+
725
+ export const RISK_LEVEL = Object.freeze({
726
+ SAFE: "safe",
727
+ LOW: "low",
728
+ MEDIUM: "medium",
729
+ HIGH: "high",
730
+ CRITICAL: "critical",
731
+ });
732
+
733
+ export const QUOTA_TYPE = Object.freeze({
734
+ CPU_PERCENT: "cpu_percent",
735
+ MEMORY_MB: "memory_mb",
736
+ DISK_MB: "disk_mb",
737
+ NETWORK_KBPS: "network_kbps",
738
+ PROCESS_COUNT: "process_count",
739
+ });
740
+
741
+ const _PERM_TYPE_VALUES = new Set(Object.values(PERMISSION_TYPE));
742
+ const _QUOTA_TYPE_VALUES = new Set(Object.values(QUOTA_TYPE));
743
+
744
+ // V2 audit / isolation state
745
+ const _v2Isolations = [];
746
+
747
+ // QUOTA_TYPE → internal quota-field mapping (maps canonical Phase 87 names
748
+ // onto the pre-existing sandbox.quota fields).
749
+ const _QUOTA_FIELD_MAP = {
750
+ [QUOTA_TYPE.CPU_PERCENT]: "cpu",
751
+ [QUOTA_TYPE.MEMORY_MB]: "memory",
752
+ [QUOTA_TYPE.DISK_MB]: "storage",
753
+ [QUOTA_TYPE.NETWORK_KBPS]: "network",
754
+ [QUOTA_TYPE.PROCESS_COUNT]: "processCount",
755
+ };
756
+
757
+ function _requireSandbox(sandboxId) {
758
+ const s = activeSandboxes.get(sandboxId);
759
+ if (!s) throw new Error(`Sandbox not found: ${sandboxId}`);
760
+ return s;
761
+ }
762
+
763
+ /**
764
+ * Pause a sandbox: active → paused (no execution allowed while paused).
765
+ */
766
+ export function pauseSandboxV2(db, sandboxId) {
767
+ ensureSandboxTables(db);
768
+ const sandbox = _requireSandbox(sandboxId);
769
+ if (sandbox.status === SANDBOX_STATUS.PAUSED)
770
+ throw new Error("Sandbox already paused");
771
+ if (sandbox.status === SANDBOX_STATUS.TERMINATED)
772
+ throw new Error("Cannot pause terminated sandbox");
773
+ const prev = sandbox.status;
774
+ sandbox.status = SANDBOX_STATUS.PAUSED;
775
+ db.prepare(
776
+ `UPDATE sandbox_instances SET status = ?, updated_at = datetime('now') WHERE id = ?`,
777
+ ).run(SANDBOX_STATUS.PAUSED, sandboxId);
778
+ logAudit(db, sandboxId, "pause", { previousStatus: prev });
779
+ return { id: sandboxId, status: SANDBOX_STATUS.PAUSED, previousStatus: prev };
780
+ }
781
+
782
+ /**
783
+ * Resume a paused sandbox back to active.
784
+ */
785
+ export function resumeSandboxV2(db, sandboxId) {
786
+ ensureSandboxTables(db);
787
+ const sandbox = _requireSandbox(sandboxId);
788
+ if (sandbox.status !== SANDBOX_STATUS.PAUSED)
789
+ throw new Error(`Cannot resume: sandbox is ${sandbox.status}, not paused`);
790
+ sandbox.status = "active";
791
+ db.prepare(
792
+ `UPDATE sandbox_instances SET status = 'active', updated_at = datetime('now') WHERE id = ?`,
793
+ ).run(sandboxId);
794
+ logAudit(db, sandboxId, "resume", {});
795
+ return { id: sandboxId, status: "active" };
796
+ }
797
+
798
+ /**
799
+ * Terminate a sandbox (canonical name for destroy; records TERMINATED status).
800
+ */
801
+ export function terminateSandboxV2(db, sandboxId, reason = null) {
802
+ ensureSandboxTables(db);
803
+ const sandbox = _requireSandbox(sandboxId);
804
+ const prev = sandbox.status;
805
+ sandbox.status = SANDBOX_STATUS.TERMINATED;
806
+ activeSandboxes.delete(sandboxId);
807
+ db.prepare(
808
+ `UPDATE sandbox_instances SET status = ?, updated_at = datetime('now') WHERE id = ?`,
809
+ ).run(SANDBOX_STATUS.TERMINATED, sandboxId);
810
+ logAudit(db, sandboxId, "terminate", { previousStatus: prev, reason });
811
+ return {
812
+ id: sandboxId,
813
+ status: SANDBOX_STATUS.TERMINATED,
814
+ previousStatus: prev,
815
+ reason,
816
+ };
817
+ }
818
+
819
+ /**
820
+ * Set a single quota value keyed by QUOTA_TYPE. Validates type; merges into
821
+ * existing quota object instead of replacing.
822
+ */
823
+ export function setQuotaTyped(db, sandboxId, quotaType, limit) {
824
+ ensureSandboxTables(db);
825
+ if (!_QUOTA_TYPE_VALUES.has(quotaType))
826
+ throw new Error(`Invalid quotaType: ${quotaType}`);
827
+ if (!Number.isFinite(limit) || limit < 0)
828
+ throw new Error(`Invalid limit: ${limit}`);
829
+ const sandbox = _requireSandbox(sandboxId);
830
+ const field = _QUOTA_FIELD_MAP[quotaType];
831
+ const nextQuota = { ...sandbox.quota, [field]: limit };
832
+ sandbox.quota = nextQuota;
833
+ db.prepare(
834
+ `UPDATE sandbox_instances SET quota = ?, updated_at = datetime('now') WHERE id = ?`,
835
+ ).run(JSON.stringify(nextQuota), sandboxId);
836
+ logAudit(db, sandboxId, "set-quota-typed", { quotaType, limit, field });
837
+ return { id: sandboxId, quotaType, limit, quota: nextQuota };
838
+ }
839
+
840
+ /**
841
+ * Explicit permission enforcement (boolean result + optional throw on denied).
842
+ * Supports filesystem / network / syscall permission types.
843
+ */
844
+ export function enforcePermission(
845
+ sandbox,
846
+ { type, target, mode, throwOnDeny = false } = {},
847
+ ) {
848
+ if (!_PERM_TYPE_VALUES.has(type))
849
+ throw new Error(`Invalid permission type: ${type}`);
850
+ if (!sandbox || !sandbox.permissions)
851
+ throw new Error("Sandbox or permissions missing");
852
+ let allowed = false;
853
+ if (type === PERMISSION_TYPE.FILESYSTEM) {
854
+ allowed = checkFilePermission(sandbox.permissions, target, mode || "read");
855
+ } else if (type === PERMISSION_TYPE.NETWORK) {
856
+ allowed = checkNetworkPermission(sandbox.permissions, target);
857
+ } else if (type === PERMISSION_TYPE.SYSCALL) {
858
+ allowed = checkSystemCallPermission(sandbox.permissions, target);
859
+ } else {
860
+ // IPC / PROCESS — not implemented in base library; treat as denied-by-default
861
+ allowed = false;
862
+ }
863
+ if (!allowed && throwOnDeny)
864
+ throw new Error(`Permission denied: ${type} ${mode || ""} ${target}`);
865
+ return { allowed, type, target, mode: mode || null };
866
+ }
867
+
868
+ /**
869
+ * Per-type quota check. Returns {ok, current, limit, remaining}.
870
+ */
871
+ export function checkQuotaV2(sandbox, quotaType, amount = 0) {
872
+ if (!_QUOTA_TYPE_VALUES.has(quotaType))
873
+ throw new Error(`Invalid quotaType: ${quotaType}`);
874
+ if (!sandbox || !sandbox.quota) throw new Error("Sandbox or quota missing");
875
+ const field = _QUOTA_FIELD_MAP[quotaType];
876
+ const limit = sandbox.quota[field];
877
+ const current = (sandbox.resourceUsage?.[field] || 0) + amount;
878
+ const remaining = limit != null ? Math.max(0, limit - current) : Infinity;
879
+ return {
880
+ quotaType,
881
+ field,
882
+ limit: limit ?? null,
883
+ current,
884
+ remaining,
885
+ ok: limit == null || current <= limit,
886
+ };
887
+ }
888
+
889
+ /**
890
+ * Map a risk score (0..100) to a RISK_LEVEL enum value.
891
+ * safe < 20 ≤ low < 40 ≤ medium < 60 ≤ high < 80 ≤ critical
892
+ */
893
+ export function getRiskLevel(score) {
894
+ const n = Number(score) || 0;
895
+ if (n < 20) return RISK_LEVEL.SAFE;
896
+ if (n < 40) return RISK_LEVEL.LOW;
897
+ if (n < 60) return RISK_LEVEL.MEDIUM;
898
+ if (n < 80) return RISK_LEVEL.HIGH;
899
+ return RISK_LEVEL.CRITICAL;
900
+ }
901
+
902
+ /**
903
+ * Calculate risk score via monitorBehavior and classify with RISK_LEVEL.
904
+ */
905
+ export function calculateRiskScore(db, sandboxId) {
906
+ const report = monitorBehavior(db, sandboxId);
907
+ const level = getRiskLevel(report.riskScore);
908
+ return {
909
+ sandboxId,
910
+ riskScore: report.riskScore,
911
+ riskLevel: level,
912
+ patterns: report.patterns,
913
+ totalEvents: report.totalEvents,
914
+ };
915
+ }
916
+
917
+ /**
918
+ * Auto-isolate a sandbox: records an isolation entry + terminates.
919
+ * Typical trigger: risk level == CRITICAL or explicit admin call.
920
+ */
921
+ export function autoIsolate(db, sandboxId, reason = "high-risk") {
922
+ ensureSandboxTables(db);
923
+ const sandbox = _requireSandbox(sandboxId);
924
+ const entry = {
925
+ id: crypto.randomUUID(),
926
+ sandboxId,
927
+ reason,
928
+ isolatedAt: new Date().toISOString(),
929
+ agentId: sandbox.agentId,
930
+ };
931
+ _v2Isolations.push(entry);
932
+ logAudit(db, sandboxId, "auto-isolate", { reason });
933
+ try {
934
+ terminateSandboxV2(db, sandboxId, reason);
935
+ } catch (_e) {
936
+ // swallow — sandbox may already be terminated
937
+ }
938
+ return entry;
939
+ }
940
+
941
+ export function listIsolations(options = {}) {
942
+ let result = [..._v2Isolations];
943
+ if (options.sandboxId)
944
+ result = result.filter((i) => i.sandboxId === options.sandboxId);
945
+ if (options.reason)
946
+ result = result.filter((i) => i.reason === options.reason);
947
+ return result;
948
+ }
949
+
950
+ /**
951
+ * Filtered audit log — time range + event types + limit.
952
+ */
953
+ export function filterAuditLog(db, sandboxId, options = {}) {
954
+ let entries = [...auditLog];
955
+ if (sandboxId) entries = entries.filter((e) => e.sandboxId === sandboxId);
956
+ if (options.eventTypes && options.eventTypes.length > 0) {
957
+ const set = new Set(options.eventTypes);
958
+ entries = entries.filter((e) => set.has(e.action));
959
+ }
960
+ if (options.timeRange) {
961
+ const from = options.timeRange.from
962
+ ? new Date(options.timeRange.from).getTime()
963
+ : null;
964
+ const to = options.timeRange.to
965
+ ? new Date(options.timeRange.to).getTime()
966
+ : null;
967
+ entries = entries.filter((e) => {
968
+ const t = new Date(e.timestamp).getTime();
969
+ if (from != null && t < from) return false;
970
+ if (to != null && t > to) return false;
971
+ return true;
972
+ });
973
+ }
974
+ if (options.limit) entries = entries.slice(-options.limit);
975
+ return entries;
976
+ }
977
+
978
+ /**
979
+ * Extended V2 stats: per-status sandbox counts + audit summary + isolations.
980
+ */
981
+ export function getSandboxStatsV2() {
982
+ const byStatus = {};
983
+ for (const s of activeSandboxes.values()) {
984
+ const st = s.status || "unknown";
985
+ byStatus[st] = (byStatus[st] || 0) + 1;
986
+ }
987
+ const auditByAction = {};
988
+ for (const e of auditLog) {
989
+ auditByAction[e.action] = (auditByAction[e.action] || 0) + 1;
990
+ }
991
+ return {
992
+ totalSandboxes: activeSandboxes.size,
993
+ byStatus,
994
+ auditEventCount: auditLog.length,
995
+ auditByAction,
996
+ isolations: {
997
+ total: _v2Isolations.length,
998
+ byReason: _v2Isolations.reduce((acc, i) => {
999
+ acc[i.reason] = (acc[i.reason] || 0) + 1;
1000
+ return acc;
1001
+ }, {}),
1002
+ },
1003
+ };
698
1004
  }