diff --git a/agent-passbook-prototype/README.md b/agent-passbook-prototype/README.md new file mode 100644 index 0000000..7db4744 --- /dev/null +++ b/agent-passbook-prototype/README.md @@ -0,0 +1,14 @@ +# Agent Passbook Prototype + +Prototype repository for tracking agent-issued passbooks within the NANDA organization. This project defines a JSON schema for passbook records, provides a sample passbook, and includes a small resolver utility to inspect passbook files. + +## Layout +- `schemas/passbook.json`: JSON Schema describing a passbook document. +- `examples/example-passbook-agentA.json`: Example passbook document adhering to the schema. +- `service/passbook-resolver.py`: Utility for loading and summarizing passbook files. + +## Usage +```bash +python service/passbook-resolver.py examples/example-passbook-agentA.json +``` +The resolver reports validation status and prints a concise summary of the passbook contents. diff --git a/agent-passbook-prototype/examples/example-passbook-agentA.json b/agent-passbook-prototype/examples/example-passbook-agentA.json new file mode 100644 index 0000000..328d1fe --- /dev/null +++ b/agent-passbook-prototype/examples/example-passbook-agentA.json @@ -0,0 +1,24 @@ +{ + "id": "passbook-agentA-0001", + "agent": { + "name": "Agent A", + "unit": "Reconnaissance", + "contact": "agent.a@nanda.example" + }, + "issued_at": "2024-06-01T12:00:00Z", + "entries": [ + { + "timestamp": "2024-06-02T09:15:00Z", + "event": "Passbook issued", + "authority": "HQ Registrar", + "notes": "Initial issuance" + }, + { + "timestamp": "2024-06-05T14:32:00Z", + "event": "Clearance verified", + "authority": "Operations Control", + "notes": "Validated for field deployment" + } + ], + "checksum": "c0ffee89" +} diff --git a/agent-passbook-prototype/schemas/passbook.json b/agent-passbook-prototype/schemas/passbook.json new file mode 100644 index 0000000..7870f31 --- /dev/null +++ b/agent-passbook-prototype/schemas/passbook.json @@ -0,0 +1,64 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://nanda.org/agent-passbook.schema.json", + "title": "Agent Passbook", + "type": "object", + "required": ["id", "agent", "issued_at", "entries"], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for this passbook" + }, + "agent": { + "type": "object", + "required": ["name", "unit"], + "additionalProperties": false, + "properties": { + "name": { "type": "string" }, + "unit": { "type": "string" }, + "contact": { + "type": "string", + "description": "Preferred contact channel for the issuing agent" + } + } + }, + "issued_at": { + "type": "string", + "format": "date-time", + "description": "ISO timestamp when the passbook was issued" + }, + "entries": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["timestamp", "event", "authority"], + "additionalProperties": false, + "properties": { + "timestamp": { + "type": "string", + "format": "date-time", + "description": "ISO timestamp for the event" + }, + "event": { + "type": "string", + "description": "Short description of the event" + }, + "authority": { + "type": "string", + "description": "Person or system authorizing the entry" + }, + "notes": { + "type": "string", + "description": "Optional supporting detail" + } + } + } + }, + "checksum": { + "type": "string", + "description": "Optional checksum for integrity verification" + } + } +} diff --git a/agent-passbook-prototype/service/passbook-resolver.py b/agent-passbook-prototype/service/passbook-resolver.py new file mode 100644 index 0000000..b458e21 --- /dev/null +++ b/agent-passbook-prototype/service/passbook-resolver.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +"""Simple resolver to load and validate agent passbook files.""" + +import argparse +import json +from datetime import datetime +from pathlib import Path +from typing import Any, Dict, List + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Load and validate a passbook file.") + parser.add_argument( + "passbook", + type=Path, + help="Path to the passbook JSON file", + ) + parser.add_argument( + "--schema", + type=Path, + default=Path(__file__).resolve().parent.parent / "schemas" / "passbook.json", + help="Path to the passbook JSON schema (for reference)", + ) + return parser.parse_args() + + +def load_json(file_path: Path) -> Dict[str, Any]: + with file_path.open("r", encoding="utf-8") as handle: + return json.load(handle) + + +def validate_datetime(value: str) -> bool: + try: + datetime.fromisoformat(value.replace("Z", "+00:00")) + except ValueError: + return False + return True + + +def validate_passbook(document: Dict[str, Any]) -> List[str]: + errors: List[str] = [] + + for field in ("id", "agent", "issued_at", "entries"): + if field not in document: + errors.append(f"Missing required field: {field}") + + if "agent" in document: + agent = document["agent"] + if not isinstance(agent, dict): + errors.append("agent must be an object") + else: + for field in ("name", "unit"): + if field not in agent: + errors.append(f"agent.{field} is required") + + if "issued_at" in document and not validate_datetime(str(document["issued_at"])): + errors.append("issued_at must be an ISO 8601 timestamp") + + entries = document.get("entries") + if not isinstance(entries, list) or len(entries) == 0: + errors.append("entries must be a non-empty array") + else: + for index, entry in enumerate(entries): + if not isinstance(entry, dict): + errors.append(f"entries[{index}] must be an object") + continue + for field in ("timestamp", "event", "authority"): + if field not in entry: + errors.append(f"entries[{index}].{field} is required") + timestamp = entry.get("timestamp") + if timestamp is not None and not validate_datetime(str(timestamp)): + errors.append(f"entries[{index}].timestamp must be an ISO 8601 timestamp") + + return errors + + +def summarize_passbook(document: Dict[str, Any]) -> str: + agent = document.get("agent", {}) + entries = document.get("entries", []) + issued_at = document.get("issued_at", "unknown time") + lines = [ + f"Passbook: {document.get('id', 'unknown id')}", + f"Agent: {agent.get('name', 'unknown name')} (unit: {agent.get('unit', 'unknown')})", + f"Issued at: {issued_at}", + "Entries:", + ] + for entry in entries: + lines.append( + f" - {entry.get('timestamp', 'unknown time')}: {entry.get('event', 'unknown event')}" + f" (authority: {entry.get('authority', 'unknown')})" + ) + return "\n".join(lines) + + +def main() -> None: + args = parse_args() + document = load_json(args.passbook) + errors = validate_passbook(document) + + print(f"Loaded passbook from {args.passbook}") + print(f"Schema reference: {args.schema}") + + if errors: + print("Validation: FAILED") + for issue in errors: + print(f" - {issue}") + else: + print("Validation: PASSED") + print(summarize_passbook(document)) + + +if __name__ == "__main__": + main()