network-ai 3.2.1 → 3.2.3

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **The plug-and-play AI agent orchestrator for TypeScript/Node.js -- connect 12 agent frameworks with zero glue code**
4
4
 
5
- [![Release](https://img.shields.io/badge/release-v3.2.1-blue.svg)](https://github.com/jovanSAPFIONEER/Network-AI/releases)
5
+ [![Release](https://img.shields.io/badge/release-v3.2.3-blue.svg)](https://github.com/jovanSAPFIONEER/Network-AI/releases)
6
6
  [![ClawHub](https://img.shields.io/badge/ClawHub-network--ai-orange.svg)](https://clawhub.ai/skills/network-ai)
7
7
  [![Node.js](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg)](https://nodejs.org)
8
8
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178C6.svg)](https://typescriptlang.org)
@@ -15,12 +15,13 @@
15
15
 
16
16
  > **Legacy Users:** This skill works with **Clawdbot** and **Moltbot** (now OpenClaw). If you're searching for *Moltbot Security*, *Clawdbot Swarm*, or *Moltbot multi-agent* -- you're in the right place!
17
17
 
18
- Network-AI is a framework-agnostic multi-agent orchestrator that connects LLM agents across **12 frameworks** -- LangChain, AutoGen, CrewAI, OpenAI Assistants, LlamaIndex, Semantic Kernel, Haystack, DSPy, Agno, MCP, OpenClaw, and custom adapters. It provides shared blackboard coordination, built-in security (AES-256, HMAC tokens, rate limiting), content quality gates with hallucination detection, and agentic workflow patterns (parallel execution, voting, chaining). Zero dependencies per adapter -- bring your own framework SDK and start building multi-agent systems in minutes.
18
+ Network-AI is a framework-agnostic multi-agent orchestrator and **behavioral control plane** that connects LLM agents across **12 frameworks** -- LangChain, AutoGen, CrewAI, OpenAI Assistants, LlamaIndex, Semantic Kernel, Haystack, DSPy, Agno, MCP, OpenClaw, and custom adapters. It provides shared blackboard coordination with atomic commits, built-in security (AES-256, HMAC tokens, rate limiting), content quality gates with hallucination detection, compliance enforcement, and agentic workflow patterns (parallel fan-out/fan-in, voting, chaining). Zero dependencies per adapter -- bring your own framework SDK and start building governed multi-agent systems in minutes.
19
19
 
20
20
  **Why Network-AI?**
21
21
  - **Framework-agnostic** -- Not locked to one LLM provider or agent SDK
22
- - **Production security** -- Encryption, audit trails, rate limiting built in
23
- - **Swarm intelligence** -- Parallel execution, voting, chain-of-agents patterns
22
+ - **Governance layer** -- Permission gating, audit trails, budget ceilings, and compliance enforcement across all agents
23
+ - **Shared state** -- Atomic blackboard with conflict resolution for safe parallel agent coordination (fan-out/fan-in)
24
+ - **Production security** -- AES-256 encryption, HMAC audit logs, rate limiting, input sanitization
24
25
  - **Zero config** -- Works out of the box with `createSwarmOrchestrator()`
25
26
 
26
27
  ## Hello World -- Get Running in 60 Seconds
@@ -131,12 +132,15 @@ Network-AI wraps your agent swarm with **file-system mutexes**, **atomic commits
131
132
  - **Cryptographic Audit Logs** -- Tamper-evident signed audit trail with chain continuation
132
133
  - **Secure Gateway** -- Integrated security layer wrapping all operations
133
134
 
134
- ### Operational Safety
135
+ ### Operational Safety & Governance
135
136
  - **Swarm Guard** -- Prevents "Handoff Tax" (wasted tokens) and detects silent agent failures
136
137
  - **Atomic Commits** -- File-system mutexes prevent split-brain in concurrent writes
137
138
  - **Priority-Based Preemption** -- Higher-priority agents preempt lower-priority writes on same-key conflicts (`priority-wins` strategy)
138
139
  - **Cost Awareness** -- Token budget tracking with automatic SafetyShutdown
139
140
  - **Budget-Aware Handoffs** -- `intercept-handoff` command wraps `sessions_send` with budget checks
141
+ - **`--active-grants` Observability** -- Real-time view of which agents hold access to which APIs, with TTL countdown
142
+ - **`--audit-summary` Observability** -- Per-agent and per-resource breakdown of permission requests, grants, and denials
143
+ - **Justification Hardening** -- 16-pattern prompt-injection detector, keyword-stuffing defense, structural coherence scoring
140
144
 
141
145
  ## Project Structure
142
146
 
@@ -190,6 +194,7 @@ Network-AI/
190
194
  |-- test-standalone.ts # Core orchestrator tests (79 tests)
191
195
  |-- test-security.ts # Security module tests (33 tests)
192
196
  |-- test-adapters.ts # Adapter system tests (139 tests)
197
+ |-- test-priority.ts # Priority & preemption tests (64 tests)
193
198
  |-- test-ai-quality.ts # AI quality gate demo
194
199
  |-- test.ts # Full integration test suite
195
200
  ```
@@ -341,6 +346,65 @@ Expires: 2026-02-04T15:30:00Z
341
346
  Restrictions: read_only, max_records:100
342
347
  ```
343
348
 
349
+ #### 3a. View Active Grants
350
+
351
+ See which agents currently hold access to which APIs:
352
+
353
+ ```bash
354
+ # Human-readable
355
+ python scripts/check_permission.py --active-grants
356
+
357
+ # Filter by agent
358
+ python scripts/check_permission.py --active-grants --agent data_analyst
359
+
360
+ # Machine-readable JSON
361
+ python scripts/check_permission.py --active-grants --json
362
+ ```
363
+
364
+ Output:
365
+ ```
366
+ Active Grants:
367
+ ======================================================================
368
+ Agent: data_analyst
369
+ Resource: DATABASE
370
+ Scope: read:orders
371
+ Token: grant_c1ea828897...
372
+ Remaining: 4.4 min
373
+ Restrictions: read_only, max_records:100
374
+ ------------------------------------------------------------------
375
+
376
+ Total: 1 active, 0 expired
377
+ ```
378
+
379
+ #### 3b. Audit Summary
380
+
381
+ Summarize permission activity across all agents:
382
+
383
+ ```bash
384
+ # Human-readable
385
+ python scripts/check_permission.py --audit-summary
386
+
387
+ # Last 50 entries, JSON output
388
+ python scripts/check_permission.py --audit-summary --last 50 --json
389
+ ```
390
+
391
+ Output:
392
+ ```
393
+ Audit Summary
394
+ ======================================================================
395
+ Requests: 12
396
+ Grants: 9
397
+ Denials: 3
398
+ Grant Rate: 75%
399
+
400
+ By Agent:
401
+ --------------------------------------------------
402
+ Agent Requests Grants Denials
403
+ data_analyst 4 3 1
404
+ orchestrator 5 4 1
405
+ strategy_advisor 3 2 1
406
+ ```
407
+
344
408
  #### 4. Use the Blackboard
345
409
 
346
410
  ```bash
@@ -359,7 +423,44 @@ python scripts/blackboard.py commit "chg_001"
359
423
  python scripts/blackboard.py list
360
424
  ```
361
425
 
362
- #### 5. Priority-Based Conflict Resolution (Phase 3)
426
+ #### 5. Fan-Out / Fan-In with Shared Blackboard
427
+
428
+ Coordinate multiple specialized agents working on independent subtasks, then merge results:
429
+
430
+ ```typescript
431
+ import { LockedBlackboard } from 'network-ai';
432
+ import { Logger } from 'network-ai';
433
+
434
+ const logger = Logger.create('fan-out');
435
+ const board = new LockedBlackboard('.', logger, { conflictResolution: 'first-commit-wins' });
436
+
437
+ // Fan-out: each agent writes to its own section
438
+ const agents = ['reliability', 'security', 'cost', 'operations', 'performance'];
439
+
440
+ for (const pillar of agents) {
441
+ // Each agent evaluates independently, writes to its own key
442
+ const id = board.propose(`eval:${pillar}`, { score: Math.random(), findings: [] }, pillar);
443
+ board.validate(id, 'orchestrator');
444
+ board.commit(id);
445
+ }
446
+
447
+ // Fan-in: orchestrator reads all results and merges
448
+ const results = agents.map(pillar => ({
449
+ pillar,
450
+ ...board.read(`eval:${pillar}`)
451
+ }));
452
+
453
+ const summary = board.propose('eval:summary', {
454
+ overall: results.reduce((sum, r) => sum + r.score, 0) / results.length,
455
+ pillars: results
456
+ }, 'orchestrator');
457
+ board.validate(summary, 'orchestrator');
458
+ board.commit(summary);
459
+ ```
460
+
461
+ This pattern works with any framework adapter -- LangChain agents, AutoGen agents, CrewAI crews, or any mix. The blackboard ensures no agent overwrites another's results.
462
+
463
+ #### 6. Priority-Based Conflict Resolution (Phase 3)
363
464
 
364
465
  ```typescript
365
466
  import { LockedBlackboard } from 'network-ai';
@@ -384,7 +485,7 @@ board.commit(highId); // success
384
485
  board.read('shared:config'); // { mode: 'final' } -- supervisor wins
385
486
  ```
386
487
 
387
- #### 6. Check Budget Status
488
+ #### 7. Check Budget Status
388
489
 
389
490
  ```bash
390
491
  python scripts/swarm_guard.py budget-check --task-id "task_001"
@@ -441,7 +542,7 @@ The AuthGuardian evaluates requests using:
441
542
 
442
543
  | Factor | Weight | Description |
443
544
  |--------|--------|-------------|
444
- | Justification | 40% | Quality of business reason |
545
+ | Justification | 40% | Quality of business reason (hardened against prompt injection) |
445
546
  | Trust Level | 30% | Agent's established trust |
446
547
  | Risk Assessment | 30% | Resource sensitivity + scope |
447
548
 
@@ -530,10 +631,11 @@ python scripts/revoke_token.py --list-expired
530
631
  python scripts/revoke_token.py --cleanup
531
632
  ```
532
633
 
533
- **Test results (251 total):**
634
+ **Test results (315 total):**
534
635
  - `test-standalone.ts` -- 79 passed (blackboard, auth, integration, persistence, parallelization, coding domain, quality gate)
535
636
  - `test-security.ts` -- 33 passed (tokens, sanitization, rate limiting, encryption, permissions, audit)
536
637
  - `test-adapters.ts` -- 139 passed (12 adapters: Custom, LangChain, AutoGen, CrewAI, MCP, LlamaIndex, Semantic Kernel, OpenAI Assistants, Haystack, DSPy, Agno + registry routing, integration, edge cases)
638
+ - `test-priority.ts` -- 64 passed (priority-based preemption, conflict resolution, constructor overloads, backward compatibility)
537
639
 
538
640
  ## Audit Trail
539
641
 
@@ -627,18 +729,43 @@ If you find Network-AI useful, **give it a star** -- it helps others discover th
627
729
 
628
730
  **Compatible with 12 agent frameworks: OpenClaw, LangChain, AutoGen, CrewAI, MCP, LlamaIndex, Semantic Kernel, OpenAI Assistants, Haystack, DSPy, Agno, and any custom adapter**
629
731
 
732
+ ## Competitive Comparison
733
+
734
+ How Network-AI compares to other multi-agent frameworks:
735
+
736
+ | Capability | Network-AI | LangChain/LangGraph | AutoGen/AG2 | CrewAI | Claude SDK |
737
+ |---|---|---|---|---|---|
738
+ | **Multi-framework support** | 12 adapters | LangChain only | AutoGen only | CrewAI only | Claude only |
739
+ | **Shared state (blackboard)** | Atomic commits, TTL, priority | LangGraph state | Shared context | Shared memory | Project memory |
740
+ | **Conflict resolution** | Priority preemption, last-write-wins | None | None | None | None |
741
+ | **Fan-out / fan-in** | Native (parallel + merge) | LangGraph branches | Group chat | Parallel tasks | Subagents |
742
+ | **Permission gating** | AuthGuardian (weighted scoring) | None | None | None | None |
743
+ | **Budget tracking** | Token ceiling + per-task budgets | Callbacks only | None | None | None |
744
+ | **Audit trail** | HMAC-signed, tamper-evident | None | None | None | None |
745
+ | **Encryption at rest** | AES-256-GCM | None | None | None | None |
746
+ | **Observability** | `--active-grants`, `--audit-summary` | LangSmith (SaaS) | None | None | None |
747
+ | **Rate limiting** | Per-agent with lockout | None | None | None | None |
748
+ | **Justification hardening** | 16-pattern injection defense | None | None | None | None |
749
+ | **Language** | TypeScript/Node.js | Python | Python | Python | Python |
750
+ | **Dependencies** | Zero (per adapter) | Heavy | Heavy | Heavy | Moderate |
751
+ | **License** | MIT | MIT | CC-BY-4.0 | MIT | MIT |
752
+
753
+ **Key differentiator:** Network-AI is the only framework that combines multi-framework orchestration with a governance layer (permissions, audit, encryption, budget enforcement). Other frameworks focus on one LLM provider; Network-AI wraps all of them.
754
+
630
755
  ## Related Concepts
631
756
 
632
757
  Network-AI fits into the broader AI agent ecosystem:
633
758
 
634
759
  - **Multi-Agent Systems** -- Coordinate multiple AI agents working together on complex tasks
635
760
  - **Agentic AI** -- Build autonomous agents that reason, plan, and execute using LLMs
636
- - **Swarm Intelligence** -- Parallel execution patterns with voting, merging, and chain strategies
761
+ - **Behavioral Control Plane** -- Govern agent behavior with permission gating, compliance enforcement, and audit trails
762
+ - **Swarm Intelligence** -- Parallel fan-out/fan-in patterns with voting, merging, and chain strategies
637
763
  - **Model Context Protocol (MCP)** -- Standard protocol support for LLM tool integration
638
764
  - **Agent-to-Agent (A2A)** -- Inter-agent communication via shared blackboard and handoff protocol
639
765
  - **Context Engineering** -- Manage and share context across agent boundaries
640
766
  - **Agentic Workflows** -- Task decomposition, parallel processing, and synthesis pipelines
641
767
  - **LLM Orchestration** -- Route tasks to the right agent framework automatically
768
+ - **Agent Governance** -- Permission gating, budget enforcement, audit logging, and compliance monitoring
642
769
 
643
770
  If you're using LangGraph, Dify, Flowise, PraisonAI, AutoGen/AG2, CrewAI, or any other agent framework, Network-AI can integrate with it through the adapter system.
644
771
 
@@ -647,6 +774,6 @@ If you're using LangGraph, Dify, Flowise, PraisonAI, AutoGen/AG2, CrewAI, or any
647
774
  <details>
648
775
  <summary>Keywords (for search)</summary>
649
776
 
650
- ai-agents, agentic-ai, multi-agent, multi-agent-systems, multi-agent-system, agent-framework, ai-agent-framework, agentic-framework, agentic-workflow, llm, llm-agents, llm-agent, large-language-models, generative-ai, genai, orchestration, ai-orchestration, swarm, swarm-intelligence, autonomous-agents, agents, ai, typescript, nodejs, mcp, model-context-protocol, a2a, agent-to-agent, function-calling, tool-integration, context-engineering, rag, ai-safety, multi-agents-collaboration, multi-agents, aiagents, aiagentframework, plug-and-play, adapter-registry, blackboard-pattern, agent-coordination, agent-handoffs, token-permissions, budget-tracking, cost-awareness, atomic-commits, hallucination-detection, content-quality-gate, OpenClaw, Clawdbot, Moltbot, Clawdbot Swarm, Moltbot Security, Moltbot multi-agent, OpenClaw skills, AgentSkills, LangChain adapter, LangGraph, AutoGen adapter, AG2, CrewAI adapter, MCP adapter, LlamaIndex adapter, Semantic Kernel adapter, OpenAI Assistants adapter, Haystack adapter, DSPy adapter, Agno adapter, Phidata adapter, Dify, Flowise, PraisonAI, custom-adapter, AES-256 encryption, HMAC tokens, rate limiting, input sanitization, privilege escalation prevention, ClawHub, clawhub, agentic-rag, deep-research, workflow-orchestration, ai-assistant, ai-tools, developer-tools, open-source
777
+ ai-agents, agentic-ai, multi-agent, multi-agent-systems, multi-agent-system, agent-framework, ai-agent-framework, agentic-framework, agentic-workflow, llm, llm-agents, llm-agent, large-language-models, generative-ai, genai, orchestration, ai-orchestration, swarm, swarm-intelligence, autonomous-agents, agents, ai, typescript, nodejs, mcp, model-context-protocol, a2a, agent-to-agent, function-calling, tool-integration, context-engineering, rag, ai-safety, multi-agents-collaboration, multi-agents, aiagents, aiagentframework, plug-and-play, adapter-registry, blackboard-pattern, agent-coordination, agent-handoffs, token-permissions, budget-tracking, cost-awareness, atomic-commits, hallucination-detection, content-quality-gate, behavioral-control-plane, governance-layer, compliance-enforcement, fan-out-fan-in, agent-observability, permission-gating, audit-trail, OpenClaw, Clawdbot, Moltbot, Clawdbot Swarm, Moltbot Security, Moltbot multi-agent, OpenClaw skills, AgentSkills, LangChain adapter, LangGraph, AutoGen adapter, AG2, CrewAI adapter, MCP adapter, LlamaIndex adapter, Semantic Kernel adapter, OpenAI Assistants adapter, Haystack adapter, DSPy adapter, Agno adapter, Phidata adapter, Dify, Flowise, PraisonAI, custom-adapter, AES-256 encryption, HMAC tokens, rate limiting, input sanitization, privilege escalation prevention, ClawHub, clawhub, agentic-rag, deep-research, workflow-orchestration, ai-assistant, ai-tools, developer-tools, open-source
651
778
 
652
779
  </details>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "network-ai",
3
- "version": "3.2.1",
3
+ "version": "3.2.3",
4
4
  "description": "AI agent orchestration framework for TypeScript/Node.js - plug-and-play multi-agent coordination with 12 frameworks (LangChain, AutoGen, CrewAI, OpenAI Assistants, LlamaIndex, Semantic Kernel, Haystack, DSPy, Agno, MCP, OpenClaw). Built-in security, swarm intelligence, and agentic workflow patterns.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -8,11 +8,17 @@ Evaluates permission requests for accessing sensitive resources
8
8
  Usage:
9
9
  python check_permission.py --agent AGENT_ID --resource RESOURCE_TYPE \
10
10
  --justification "REASON" [--scope SCOPE]
11
+ python check_permission.py --active-grants [--agent AGENT_ID] [--json]
12
+ python check_permission.py --audit-summary [--last N] [--json]
11
13
 
12
- Example:
14
+ Examples:
13
15
  python check_permission.py --agent data_analyst --resource DATABASE \
14
16
  --justification "Need customer order history for sales report" \
15
17
  --scope "read:orders"
18
+
19
+ python check_permission.py --active-grants
20
+ python check_permission.py --active-grants --agent data_analyst --json
21
+ python check_permission.py --audit-summary --last 20 --json
16
22
  """
17
23
 
18
24
  import argparse
@@ -355,35 +361,277 @@ def evaluate_permission(agent_id: str, resource_type: str,
355
361
  }
356
362
 
357
363
 
364
+ def list_active_grants(agent_filter: Optional[str] = None, as_json: bool = False) -> int:
365
+ """
366
+ Show which agents currently hold access to which APIs with expiry times.
367
+
368
+ Reads data/active_grants.json, filters out expired grants,
369
+ and displays remaining grants with TTL.
370
+ """
371
+ if not GRANTS_FILE.exists():
372
+ if as_json:
373
+ print(json.dumps({"grants": [], "total": 0, "expired_cleaned": 0}))
374
+ else:
375
+ print("No active grants. (No grants file found.)")
376
+ return 0
377
+
378
+ try:
379
+ grants = json.loads(GRANTS_FILE.read_text())
380
+ except (json.JSONDecodeError, OSError):
381
+ if as_json:
382
+ print(json.dumps({"error": "Could not read grants file"}))
383
+ else:
384
+ print("Error: Could not read grants file.")
385
+ return 1
386
+
387
+ now = datetime.now(timezone.utc)
388
+ active: list[dict[str, Any]] = []
389
+ expired_count = 0
390
+
391
+ for token, grant in grants.items():
392
+ try:
393
+ expires_at = datetime.fromisoformat(grant["expires_at"])
394
+ except (KeyError, ValueError):
395
+ expired_count += 1
396
+ continue
397
+
398
+ if expires_at <= now:
399
+ expired_count += 1
400
+ continue
401
+
402
+ if agent_filter and grant.get("agent_id") != agent_filter:
403
+ continue
404
+
405
+ remaining = expires_at - now
406
+ minutes_left = remaining.total_seconds() / 60
407
+
408
+ active.append({
409
+ "token": token[:16] + "..." if len(token) > 16 else token,
410
+ "token_full": token,
411
+ "agent_id": grant.get("agent_id", "unknown"),
412
+ "resource_type": grant.get("resource_type", "unknown"),
413
+ "scope": grant.get("scope"),
414
+ "granted_at": grant.get("granted_at", "unknown"),
415
+ "expires_at": grant["expires_at"],
416
+ "minutes_remaining": round(minutes_left, 1),
417
+ "restrictions": grant.get("restrictions", []),
418
+ })
419
+
420
+ # Sort by expiry (soonest first)
421
+ active.sort(key=lambda g: g["expires_at"])
422
+
423
+ if as_json:
424
+ # In JSON mode, include full tokens
425
+ output: dict[str, Any] = {
426
+ "grants": active,
427
+ "total": len(active),
428
+ "expired_cleaned": expired_count,
429
+ }
430
+ print(json.dumps(output, indent=2))
431
+ else:
432
+ if not active:
433
+ filter_msg = f" for agent '{agent_filter}'" if agent_filter else ""
434
+ print(f"No active grants{filter_msg}. ({expired_count} expired.)")
435
+ else:
436
+ filter_msg = f" (agent: {agent_filter})" if agent_filter else ""
437
+ print(f"Active Grants{filter_msg}:")
438
+ print(f"{'='*70}")
439
+ for g in active:
440
+ print(f" Agent: {g['agent_id']}")
441
+ print(f" Resource: {g['resource_type']}")
442
+ if g["scope"]:
443
+ print(f" Scope: {g['scope']}")
444
+ print(f" Token: {g['token']}")
445
+ print(f" Granted: {g['granted_at']}")
446
+ print(f" Expires: {g['expires_at']}")
447
+ print(f" Remaining: {g['minutes_remaining']} min")
448
+ if g["restrictions"]:
449
+ print(f" Restrictions: {', '.join(g['restrictions'])}")
450
+ print(f" {'-'*66}")
451
+ print(f"\nTotal: {len(active)} active, {expired_count} expired")
452
+
453
+ return 0
454
+
455
+
456
+ def audit_summary(last_n: int = 20, as_json: bool = False) -> int:
457
+ """
458
+ Summarize recent permission requests, grants, and denials.
459
+
460
+ Parses data/audit_log.jsonl and produces per-agent and per-resource
461
+ breakdowns plus recent activity.
462
+ """
463
+ if not AUDIT_LOG.exists():
464
+ if as_json:
465
+ print(json.dumps({"entries": 0, "summary": {}, "recent": []}))
466
+ else:
467
+ print("No audit log found. (No permission requests recorded yet.)")
468
+ return 0
469
+
470
+ entries: list[dict[str, Any]] = []
471
+ try:
472
+ with open(AUDIT_LOG, "r") as f:
473
+ for line in f:
474
+ line = line.strip()
475
+ if line:
476
+ try:
477
+ entries.append(json.loads(line))
478
+ except json.JSONDecodeError:
479
+ continue
480
+ except OSError:
481
+ if as_json:
482
+ print(json.dumps({"error": "Could not read audit log"}))
483
+ else:
484
+ print("Error: Could not read audit log.")
485
+ return 1
486
+
487
+ if not entries:
488
+ if as_json:
489
+ print(json.dumps({"entries": 0, "summary": {}, "recent": []}))
490
+ else:
491
+ print("Audit log is empty.")
492
+ return 0
493
+
494
+ # Aggregate stats
495
+ total_requests = 0
496
+ total_grants = 0
497
+ by_agent: dict[str, dict[str, int]] = {}
498
+ by_resource: dict[str, dict[str, int]] = {}
499
+
500
+ for entry in entries:
501
+ action = entry.get("action", "")
502
+ details = entry.get("details", {})
503
+ agent_id = details.get("agent_id", "unknown")
504
+ resource_type = details.get("resource_type", "unknown")
505
+
506
+ if action == "permission_request":
507
+ total_requests += 1
508
+ by_agent.setdefault(agent_id, {"requests": 0, "grants": 0})
509
+ by_agent[agent_id]["requests"] += 1
510
+ by_resource.setdefault(resource_type, {"requests": 0, "grants": 0})
511
+ by_resource[resource_type]["requests"] += 1
512
+ elif action == "permission_granted":
513
+ total_grants += 1
514
+ by_agent.setdefault(agent_id, {"requests": 0, "grants": 0})
515
+ by_agent[agent_id]["grants"] += 1
516
+ by_resource.setdefault(resource_type, {"requests": 0, "grants": 0})
517
+ by_resource[resource_type]["grants"] += 1
518
+
519
+ total_denials = total_requests - total_grants
520
+
521
+ # Recent entries (last N)
522
+ recent = entries[-last_n:]
523
+
524
+ # Time range
525
+ first_ts = entries[0].get("timestamp", "unknown")
526
+ last_ts = entries[-1].get("timestamp", "unknown")
527
+
528
+ if as_json:
529
+ output: dict[str, Any] = {
530
+ "total_entries": len(entries),
531
+ "total_requests": total_requests,
532
+ "total_grants": total_grants,
533
+ "total_denials": total_denials,
534
+ "time_range": {"first": first_ts, "last": last_ts},
535
+ "by_agent": by_agent,
536
+ "by_resource": by_resource,
537
+ "recent": recent[-last_n:],
538
+ }
539
+ print(json.dumps(output, indent=2))
540
+ else:
541
+ print("Audit Summary")
542
+ print(f"{'='*70}")
543
+ print(f" Log entries: {len(entries)}")
544
+ print(f" Time range: {first_ts}")
545
+ print(f" {last_ts}")
546
+ print(f"")
547
+ print(f" Requests: {total_requests}")
548
+ print(f" Grants: {total_grants}")
549
+ print(f" Denials: {total_denials}")
550
+ grant_rate = (total_grants / total_requests * 100) if total_requests > 0 else 0
551
+ print(f" Grant Rate: {grant_rate:.0f}%")
552
+
553
+ if by_agent:
554
+ print(f"\n By Agent:")
555
+ print(f" {'-'*50}")
556
+ print(f" {'Agent':<20} {'Requests':>10} {'Grants':>10} {'Denials':>10}")
557
+ print(f" {'-'*50}")
558
+ for agent_id, stats in sorted(by_agent.items()):
559
+ denials = stats["requests"] - stats["grants"]
560
+ print(f" {agent_id:<20} {stats['requests']:>10} {stats['grants']:>10} {denials:>10}")
561
+
562
+ if by_resource:
563
+ print(f"\n By Resource:")
564
+ print(f" {'-'*50}")
565
+ print(f" {'Resource':<20} {'Requests':>10} {'Grants':>10} {'Denials':>10}")
566
+ print(f" {'-'*50}")
567
+ for resource_type, stats in sorted(by_resource.items()):
568
+ denials = stats["requests"] - stats["grants"]
569
+ print(f" {resource_type:<20} {stats['requests']:>10} {stats['grants']:>10} {denials:>10}")
570
+
571
+ print(f"\n Recent Activity (last {min(last_n, len(recent))}):")
572
+ print(f" {'-'*66}")
573
+ for entry in recent:
574
+ ts = entry.get("timestamp", "?")[:19]
575
+ action = entry.get("action", "?")
576
+ details = entry.get("details", {})
577
+ agent_id = details.get("agent_id", "?")
578
+ resource_type = details.get("resource_type", "?")
579
+ symbol = "GRANT" if action == "permission_granted" else "REQ" if action == "permission_request" else action.upper()
580
+ print(f" {ts} [{symbol:>5}] {agent_id} -> {resource_type}")
581
+
582
+ return 0
583
+
584
+
358
585
  def main():
359
586
  parser = argparse.ArgumentParser(
360
587
  description="AuthGuardian Permission Checker",
361
588
  formatter_class=argparse.RawDescriptionHelpFormatter,
362
589
  epilog="""
363
590
  Examples:
364
- %(prog)s --agent data_analyst --resource SAP_API \\
365
- --justification "Need Q4 invoice data for quarterly report"
366
-
367
- %(prog)s --agent orchestrator --resource FINANCIAL_API \\
368
- --justification "Generating board presentation financials" \\
369
- --scope "read:revenue,read:expenses"
591
+ Check permission:
592
+ %(prog)s --agent data_analyst --resource DATABASE \\
593
+ --justification "Need Q4 invoice data for quarterly report"
594
+
595
+ List active grants:
596
+ %(prog)s --active-grants
597
+ %(prog)s --active-grants --agent data_analyst --json
598
+
599
+ View audit summary:
600
+ %(prog)s --audit-summary
601
+ %(prog)s --audit-summary --last 50 --json
370
602
  """
371
603
  )
372
-
604
+
605
+ # Action flags
606
+ parser.add_argument(
607
+ "--active-grants",
608
+ action="store_true",
609
+ help="List all active (non-expired) permission grants"
610
+ )
611
+ parser.add_argument(
612
+ "--audit-summary",
613
+ action="store_true",
614
+ help="Show audit log summary with per-agent and per-resource breakdowns"
615
+ )
616
+ parser.add_argument(
617
+ "--last",
618
+ type=int,
619
+ default=20,
620
+ help="Number of recent audit entries to show (default: 20)"
621
+ )
622
+
623
+ # Permission check args (required only for check mode)
373
624
  parser.add_argument(
374
625
  "--agent", "-a",
375
- required=True,
376
- help="Agent ID requesting permission"
626
+ help="Agent ID requesting permission (required for check; optional filter for --active-grants)"
377
627
  )
378
628
  parser.add_argument(
379
629
  "--resource", "-r",
380
- required=True,
381
630
  choices=["DATABASE", "PAYMENTS", "EMAIL", "FILE_EXPORT"],
382
631
  help="Resource type to access"
383
632
  )
384
633
  parser.add_argument(
385
634
  "--justification", "-j",
386
- required=True,
387
635
  help="Business justification for the request"
388
636
  )
389
637
  parser.add_argument(
@@ -395,29 +643,45 @@ Examples:
395
643
  action="store_true",
396
644
  help="Output result as JSON"
397
645
  )
398
-
646
+
399
647
  args = parser.parse_args()
400
-
648
+
649
+ # --- Action: --active-grants ---
650
+ if args.active_grants:
651
+ sys.exit(list_active_grants(agent_filter=args.agent, as_json=args.json))
652
+
653
+ # --- Action: --audit-summary ---
654
+ if args.audit_summary:
655
+ sys.exit(audit_summary(last_n=args.last, as_json=args.json))
656
+
657
+ # --- Default action: permission check ---
658
+ if not args.agent:
659
+ parser.error("--agent is required for permission checks")
660
+ if not args.resource:
661
+ parser.error("--resource is required for permission checks")
662
+ if not args.justification:
663
+ parser.error("--justification is required for permission checks")
664
+
401
665
  result = evaluate_permission(
402
666
  agent_id=args.agent,
403
667
  resource_type=args.resource,
404
668
  justification=args.justification,
405
669
  scope=args.scope
406
670
  )
407
-
671
+
408
672
  if args.json:
409
673
  print(json.dumps(result, indent=2))
410
674
  else:
411
675
  if result["granted"]:
412
- print("GRANTED")
676
+ print("GRANTED")
413
677
  print(f"Token: {result['token']}")
414
678
  print(f"Expires: {result['expires_at']}")
415
679
  print(f"Restrictions: {', '.join(result['restrictions'])}")
416
680
  else:
417
- print("DENIED")
681
+ print("DENIED")
418
682
  print(f"Reason: {result['reason']}")
419
-
420
- print("\n📊 Evaluation Scores:")
683
+
684
+ print("\nEvaluation Scores:")
421
685
  scores = result["scores"]
422
686
  if scores.get("justification") is not None:
423
687
  print(f" Justification: {scores['justification']:.2f}")
@@ -427,7 +691,7 @@ Examples:
427
691
  print(f" Risk Score: {scores['risk']:.2f}")
428
692
  if scores.get("weighted") is not None:
429
693
  print(f" Weighted: {scores['weighted']:.2f}")
430
-
694
+
431
695
  sys.exit(0 if result["granted"] else 1)
432
696
 
433
697