mirror of
https://github.com/blackboxprogramming/BlackRoad-Operating-System.git
synced 2026-03-17 08:57:15 -05:00
Create IP protection vault prompt system (#108)
Implements a complete vertical slice of the IP Vault feature for
BlackRoad OS, providing cryptographic timestamping and evidence
generation for ideas and intellectual property.
## Components Added
### Agent Layer
- **VaultAgent** (`agents/categories/security/vault_agent.py`):
- Deterministic text canonicalization
- Multi-hash generation (SHA-256, SHA-512, Keccak-256)
- LEO (Ledger Evidence Object) construction
- Verification text generation
- Blockchain anchoring preparation
### Backend API
- **Models** (`backend/app/models/leo.py`):
- LEO: Stores cryptographic hashes and metadata
- AnchorEvent: Audit trail for blockchain anchoring
- **Schemas** (`backend/app/schemas/leo.py`):
- LEOCreate, LEOResponse, LEODetail, LEOList
- AnchorRequest, AnchorEventResponse
- **Router** (`backend/app/routers/ip_vault.py`):
- POST /api/vault/leos - Create new LEO
- GET /api/vault/leos - List LEOs (paginated)
- GET /api/vault/leos/{id} - Get LEO details
- POST /api/vault/leos/{id}/anchor - Initiate anchoring (stub)
- GET /api/vault/leos/{id}/events - Get anchor events
### Frontend
- **API Client** (`backend/static/js/api-client.js`):
- createLEO(), getLEOs(), getLEO()
- anchorLEO(), getLEOEvents()
- **App** (`backend/static/js/apps.js`):
- loadIPVault() - Load and display LEOs
- vaultIdea() - Create new LEO from form
- viewLEO() - Show detailed LEO modal with verification
- **UI** (`backend/static/index.html`):
- Desktop icon (🔐 IP Vault)
- Window with form and list view
- Start menu integration
## Features
- **Deterministic canonicalization**: Ensures reproducible hashing
- **Multi-hash support**: SHA-256, SHA-512, Keccak-256
(Ethereum-compatible)
- **Verification instructions**: Auto-generated proof-of-authenticity
text
- **Blockchain-ready**: Prepared for Bitcoin, Litecoin, Ethereum
anchoring
- **Clean separation**: Agent logic, API, database, frontend all
decoupled
## Testing
- Python syntax validated for all new files
- JavaScript syntax validated
- VaultAgent tested end-to-end with sample idea
- All hashes computed successfully
## Next Steps
- Implement actual blockchain anchoring
- Add RoadChain integration
- Export LEOs as legal-grade PDFs
- Add user authentication to LEO creation
# Pull Request
## Description
<!-- Provide a brief description of the changes in this PR -->
## Type of Change
<!-- Mark the relevant option with an 'x' -->
- [ ] 📝 Documentation update
- [ ] 🧪 Tests only
- [ ] 🏗️ Scaffolding/stubs
- [ ] ✨ New feature
- [ ] 🐛 Bug fix
- [ ] ♻️ Refactoring
- [ ] ⚙️ Infrastructure/CI
- [ ] 📦 Dependencies update
- [ ] 🔒 Security fix
- [ ] 💥 Breaking change
## Checklist
<!-- Mark completed items with an 'x' -->
- [ ] Code follows the project's style guidelines
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
## Auto-Merge Eligibility
<!-- This section helps determine if this PR qualifies for auto-merge
-->
**Eligible for auto-merge?**
- [ ] Yes - This is a docs-only, tests-only, or small AI-generated PR
- [ ] No - Requires human review
**Reason for auto-merge eligibility:**
- [ ] Docs-only (Tier 1)
- [ ] Tests-only (Tier 2)
- [ ] Scaffolding < 200 lines (Tier 3)
- [ ] AI-generated < 500 lines (Tier 4)
- [ ] Dependency patch/minor (Tier 5)
**If not auto-merge eligible, why?**
- [ ] Breaking change
- [ ] Security-related
- [ ] Infrastructure changes
- [ ] Requires discussion
- [ ] Large PR (> 500 lines)
## Related Issues
<!-- Link to related issues -->
Closes #
Related to #
## Test Plan
<!-- Describe how you tested these changes -->
## Screenshots (if applicable)
<!-- Add screenshots for UI changes -->
---
**Note**: This PR will be automatically labeled based on files changed.
See `GITHUB_AUTOMATION_RULES.md` for details.
If this PR meets auto-merge criteria (see `AUTO_MERGE_POLICY.md`), it
will be automatically approved and merged after checks pass.
For questions about the merge queue system, see `MERGE_QUEUE_PLAN.md`.
This commit is contained in:
@@ -1 +1,5 @@
|
|||||||
"""Security & Compliance Agents"""
|
"""Security & Compliance Agents"""
|
||||||
|
|
||||||
|
from .vault_agent import VaultAgent
|
||||||
|
|
||||||
|
__all__ = ['VaultAgent']
|
||||||
|
|||||||
335
agents/categories/security/vault_agent.py
Normal file
335
agents/categories/security/vault_agent.py
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
"""
|
||||||
|
IP Vault Agent
|
||||||
|
|
||||||
|
Cryptographic proof-of-origin system for ideas, concepts, and intellectual property.
|
||||||
|
Produces forensically defensible evidence objects suitable for blockchain anchoring.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import re
|
||||||
|
from dataclasses import dataclass, asdict
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from agents.base import BaseAgent
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class LEO:
|
||||||
|
"""
|
||||||
|
Ledger Evidence Object (LEO)
|
||||||
|
|
||||||
|
Cryptographically signed record of an idea's origin.
|
||||||
|
Suitable for blockchain anchoring and legal defense.
|
||||||
|
"""
|
||||||
|
id: str
|
||||||
|
author: str
|
||||||
|
title: Optional[str]
|
||||||
|
canonical_size: int
|
||||||
|
sha256: str
|
||||||
|
sha512: str
|
||||||
|
keccak256: str
|
||||||
|
created_at: str
|
||||||
|
anchor_status: str = "pending"
|
||||||
|
anchor_txid: Optional[str] = None
|
||||||
|
anchor_chain: Optional[str] = None
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
"""Convert LEO to dictionary."""
|
||||||
|
return asdict(self)
|
||||||
|
|
||||||
|
|
||||||
|
class VaultAgent(BaseAgent):
|
||||||
|
"""
|
||||||
|
IP Vault cryptographic agent.
|
||||||
|
|
||||||
|
Capabilities:
|
||||||
|
- Canonical text normalization (deterministic)
|
||||||
|
- Multi-hash generation (SHA-256, SHA-512, Keccak-256)
|
||||||
|
- LEO (Ledger Evidence Object) construction
|
||||||
|
- Blockchain anchoring preparation
|
||||||
|
- Verification text generation
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(
|
||||||
|
name='vault-agent',
|
||||||
|
description='Cryptographic IP proof-of-origin system',
|
||||||
|
category='security',
|
||||||
|
version='1.0.0',
|
||||||
|
tags=['cryptography', 'ip', 'blockchain', 'evidence', 'hashing']
|
||||||
|
)
|
||||||
|
|
||||||
|
async def execute(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Execute vault operation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
params: {
|
||||||
|
'action': 'canonicalize|hash|create_leo|verify',
|
||||||
|
'idea': str, # Raw idea text
|
||||||
|
'author': str, # Default: 'Alexa'
|
||||||
|
'title': str (optional),
|
||||||
|
'leo_id': str (for verify action)
|
||||||
|
}
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
{
|
||||||
|
'status': 'success|failed',
|
||||||
|
'action': str,
|
||||||
|
'result': Dict # Action-specific result
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
action = params.get('action', 'create_leo')
|
||||||
|
idea = params.get('idea', '')
|
||||||
|
author = params.get('author', 'Alexa')
|
||||||
|
title = params.get('title')
|
||||||
|
|
||||||
|
self.logger.info(f"Vault agent: {action}")
|
||||||
|
|
||||||
|
result = {'status': 'success', 'action': action}
|
||||||
|
|
||||||
|
if action == 'canonicalize':
|
||||||
|
canonical = self._canonicalize(idea)
|
||||||
|
result['result'] = {
|
||||||
|
'canonical_text': canonical,
|
||||||
|
'original_size': len(idea),
|
||||||
|
'canonical_size': len(canonical)
|
||||||
|
}
|
||||||
|
|
||||||
|
elif action == 'hash':
|
||||||
|
canonical = self._canonicalize(idea)
|
||||||
|
hashes = self._compute_hashes(canonical)
|
||||||
|
result['result'] = {
|
||||||
|
'sha256': hashes['sha256'],
|
||||||
|
'sha512': hashes['sha512'],
|
||||||
|
'keccak256': hashes['keccak256'],
|
||||||
|
'canonical_size': len(canonical)
|
||||||
|
}
|
||||||
|
|
||||||
|
elif action == 'create_leo':
|
||||||
|
leo = self._create_leo(idea, author, title)
|
||||||
|
result['result'] = {
|
||||||
|
'leo': leo.to_dict(),
|
||||||
|
'verification_text': self._generate_verification_text(leo),
|
||||||
|
'anchoring_options': self._generate_anchoring_options(leo)
|
||||||
|
}
|
||||||
|
|
||||||
|
elif action == 'verify':
|
||||||
|
leo_id = params.get('leo_id')
|
||||||
|
verification_steps = self._generate_verification_steps()
|
||||||
|
result['result'] = {
|
||||||
|
'leo_id': leo_id,
|
||||||
|
'verification_steps': verification_steps
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _canonicalize(self, text: str) -> str:
|
||||||
|
"""
|
||||||
|
Canonicalize text to deterministic form.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- Strip leading/trailing whitespace
|
||||||
|
- Normalize internal whitespace to single spaces
|
||||||
|
- Remove empty lines
|
||||||
|
- Preserve semantic meaning exactly
|
||||||
|
- No interpretation or expansion
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: Raw input text
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Canonical text form
|
||||||
|
"""
|
||||||
|
# Strip leading/trailing whitespace
|
||||||
|
text = text.strip()
|
||||||
|
|
||||||
|
# Normalize line endings to \n
|
||||||
|
text = text.replace('\r\n', '\n').replace('\r', '\n')
|
||||||
|
|
||||||
|
# Remove empty lines
|
||||||
|
lines = [line.strip() for line in text.split('\n') if line.strip()]
|
||||||
|
|
||||||
|
# Join with single newline
|
||||||
|
text = '\n'.join(lines)
|
||||||
|
|
||||||
|
# Collapse multiple spaces to single space (within lines)
|
||||||
|
text = re.sub(r' +', ' ', text)
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
def _compute_hashes(self, canonical_text: str) -> Dict[str, str]:
|
||||||
|
"""
|
||||||
|
Compute cryptographic hashes of canonical text.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
canonical_text: Canonicalized input
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary of hashes (hex format)
|
||||||
|
"""
|
||||||
|
text_bytes = canonical_text.encode('utf-8')
|
||||||
|
|
||||||
|
# SHA-256
|
||||||
|
sha256 = hashlib.sha256(text_bytes).hexdigest()
|
||||||
|
|
||||||
|
# SHA-512
|
||||||
|
sha512 = hashlib.sha512(text_bytes).hexdigest()
|
||||||
|
|
||||||
|
# Keccak-256 (Ethereum-compatible)
|
||||||
|
keccak256 = hashlib.sha3_256(text_bytes).hexdigest()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'sha256': sha256,
|
||||||
|
'sha512': sha512,
|
||||||
|
'keccak256': keccak256
|
||||||
|
}
|
||||||
|
|
||||||
|
def _create_leo(
|
||||||
|
self,
|
||||||
|
idea: str,
|
||||||
|
author: str = 'Alexa',
|
||||||
|
title: Optional[str] = None
|
||||||
|
) -> LEO:
|
||||||
|
"""
|
||||||
|
Create a Ledger Evidence Object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
idea: Raw idea text
|
||||||
|
author: Idea author
|
||||||
|
title: Optional title
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
LEO instance
|
||||||
|
"""
|
||||||
|
canonical = self._canonicalize(idea)
|
||||||
|
hashes = self._compute_hashes(canonical)
|
||||||
|
|
||||||
|
leo = LEO(
|
||||||
|
id=str(uuid4()),
|
||||||
|
author=author,
|
||||||
|
title=title,
|
||||||
|
canonical_size=len(canonical),
|
||||||
|
sha256=hashes['sha256'],
|
||||||
|
sha512=hashes['sha512'],
|
||||||
|
keccak256=hashes['keccak256'],
|
||||||
|
created_at=datetime.utcnow().isoformat() + 'Z'
|
||||||
|
)
|
||||||
|
|
||||||
|
return leo
|
||||||
|
|
||||||
|
def _generate_verification_text(self, leo: LEO) -> str:
|
||||||
|
"""
|
||||||
|
Generate verification instructions for LEO.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
leo: LEO instance
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Verification text
|
||||||
|
"""
|
||||||
|
return f"""VERIFICATION INSTRUCTIONS
|
||||||
|
|
||||||
|
This Ledger Evidence Object (LEO) proves:
|
||||||
|
- The idea existed at {leo.created_at}
|
||||||
|
- The author is {leo.author}
|
||||||
|
- The content is cryptographically bound to the hashes below
|
||||||
|
|
||||||
|
To verify:
|
||||||
|
1. Obtain the original idea text
|
||||||
|
2. Canonicalize it using the same rules
|
||||||
|
3. Compute SHA-256 hash
|
||||||
|
4. Compare against: {leo.sha256}
|
||||||
|
|
||||||
|
If hashes match:
|
||||||
|
- The idea is authentic and unmodified
|
||||||
|
- The timestamp predates any later-origin claim
|
||||||
|
- Tampering is cryptographically impossible
|
||||||
|
|
||||||
|
This uses the SAME cryptographic primitives underlying Bitcoin/ETF custody systems.
|
||||||
|
Any blockchain anchor transaction provides additional immutable proof.
|
||||||
|
|
||||||
|
Hash fingerprints:
|
||||||
|
- SHA-256: {leo.sha256}
|
||||||
|
- SHA-512: {leo.sha512}
|
||||||
|
- Keccak-256: {leo.keccak256}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _generate_anchoring_options(self, leo: LEO) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Generate blockchain anchoring options.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
leo: LEO instance
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Anchoring options
|
||||||
|
"""
|
||||||
|
# Bitcoin OP_RETURN payload (80 bytes max)
|
||||||
|
short_hash = leo.sha256[:64] # First 32 bytes (64 hex chars)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'bitcoin': {
|
||||||
|
'method': 'OP_RETURN',
|
||||||
|
'payload': short_hash,
|
||||||
|
'payload_size': len(short_hash),
|
||||||
|
'estimated_fee_sat': 1000,
|
||||||
|
'estimated_fee_usd': 0.50,
|
||||||
|
'confirmation_time': '10-60 minutes'
|
||||||
|
},
|
||||||
|
'litecoin': {
|
||||||
|
'method': 'OP_RETURN',
|
||||||
|
'payload': short_hash,
|
||||||
|
'estimated_fee_ltc': 0.001,
|
||||||
|
'confirmation_time': '2.5-15 minutes'
|
||||||
|
},
|
||||||
|
'ethereum': {
|
||||||
|
'method': 'Contract event',
|
||||||
|
'hash': leo.keccak256,
|
||||||
|
'estimated_fee_gwei': 20,
|
||||||
|
'confirmation_time': '12-60 seconds'
|
||||||
|
},
|
||||||
|
'recommended': 'bitcoin',
|
||||||
|
'reason': 'Highest immutability and legal recognition'
|
||||||
|
}
|
||||||
|
|
||||||
|
def _generate_verification_steps(self) -> list:
|
||||||
|
"""
|
||||||
|
Generate generic verification steps.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of verification steps
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
"Obtain the LEO from the database or blockchain",
|
||||||
|
"Retrieve the original idea text",
|
||||||
|
"Apply canonicalization rules",
|
||||||
|
"Compute SHA-256 hash of canonical text",
|
||||||
|
"Compare computed hash to LEO's sha256 field",
|
||||||
|
"If match: idea is verified as authentic",
|
||||||
|
"Check blockchain for anchor transaction (if anchored)",
|
||||||
|
"Verify anchor transaction confirms before any dispute date"
|
||||||
|
]
|
||||||
|
|
||||||
|
def validate_params(self, params: Dict[str, Any]) -> bool:
|
||||||
|
"""Validate vault operation parameters."""
|
||||||
|
valid_actions = ['canonicalize', 'hash', 'create_leo', 'verify']
|
||||||
|
action = params.get('action', 'create_leo')
|
||||||
|
|
||||||
|
if action not in valid_actions:
|
||||||
|
self.logger.error(f"Invalid action: {action}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if action in ['canonicalize', 'hash', 'create_leo']:
|
||||||
|
if 'idea' not in params or not params['idea']:
|
||||||
|
self.logger.error("'idea' parameter required and cannot be empty")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if action == 'verify':
|
||||||
|
if 'leo_id' not in params:
|
||||||
|
self.logger.error("'leo_id' parameter required for verify action")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
@@ -15,8 +15,7 @@ from app.routers import (
|
|||||||
digitalocean, github, huggingface, vscode, games, browser, dashboard,
|
digitalocean, github, huggingface, vscode, games, browser, dashboard,
|
||||||
railway, vercel, stripe, twilio, slack, discord, sentry, api_health, agents,
|
railway, vercel, stripe, twilio, slack, discord, sentry, api_health, agents,
|
||||||
capture, identity_center, notifications_center, creator, compliance_ops,
|
capture, identity_center, notifications_center, creator, compliance_ops,
|
||||||
search, cloudflare, system, webhooks
|
search, cloudflare, system, webhooks, prism_static, ip_vault
|
||||||
search, cloudflare, prism_static
|
|
||||||
)
|
)
|
||||||
from app.services.crypto import rotate_plaintext_wallet_keys
|
from app.services.crypto import rotate_plaintext_wallet_keys
|
||||||
|
|
||||||
@@ -33,6 +32,7 @@ openapi_tags = [
|
|||||||
{"name": "health", "description": "BlackRoad OS service health"},
|
{"name": "health", "description": "BlackRoad OS service health"},
|
||||||
{"name": "agents", "description": "BlackRoad Agent Library - 208 AI agents across 10 categories"},
|
{"name": "agents", "description": "BlackRoad Agent Library - 208 AI agents across 10 categories"},
|
||||||
{"name": "cloudflare", "description": "Cloudflare zone, DNS, and Worker scaffolding"},
|
{"name": "cloudflare", "description": "Cloudflare zone, DNS, and Worker scaffolding"},
|
||||||
|
{"name": "IP Vault", "description": "Cryptographic proof-of-origin for ideas and intellectual property"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -158,6 +158,9 @@ app.include_router(api_health.router)
|
|||||||
# Agent Library
|
# Agent Library
|
||||||
app.include_router(agents.router)
|
app.include_router(agents.router)
|
||||||
|
|
||||||
|
# IP Vault
|
||||||
|
app.include_router(ip_vault.router)
|
||||||
|
|
||||||
# GitHub Webhooks (Phase Q automation)
|
# GitHub Webhooks (Phase Q automation)
|
||||||
app.include_router(webhooks.router)
|
app.include_router(webhooks.router)
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from app.models.identity_profile import UserProfile
|
|||||||
from app.models.notification import Notification
|
from app.models.notification import Notification
|
||||||
from app.models.creator import CreativeProject
|
from app.models.creator import CreativeProject
|
||||||
from app.models.compliance_event import ComplianceEvent
|
from app.models.compliance_event import ComplianceEvent
|
||||||
|
from app.models.leo import LEO, AnchorEvent
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"User",
|
"User",
|
||||||
@@ -40,4 +41,6 @@ __all__ = [
|
|||||||
"Notification",
|
"Notification",
|
||||||
"CreativeProject",
|
"CreativeProject",
|
||||||
"ComplianceEvent",
|
"ComplianceEvent",
|
||||||
|
"LEO",
|
||||||
|
"AnchorEvent",
|
||||||
]
|
]
|
||||||
|
|||||||
77
backend/app/models/leo.py
Normal file
77
backend/app/models/leo.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
"""LEO (Ledger Evidence Object) model"""
|
||||||
|
from sqlalchemy import Column, Integer, String, DateTime, Text
|
||||||
|
from sqlalchemy.sql import func
|
||||||
|
from app.database import Base
|
||||||
|
|
||||||
|
|
||||||
|
class LEO(Base):
|
||||||
|
"""
|
||||||
|
Ledger Evidence Object (LEO) model
|
||||||
|
|
||||||
|
Cryptographic proof-of-origin for ideas and intellectual property.
|
||||||
|
Stores hashes and metadata suitable for blockchain anchoring.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "leos"
|
||||||
|
|
||||||
|
id = Column(String(36), primary_key=True, index=True) # UUID
|
||||||
|
author = Column(String(255), nullable=False, default="Alexa")
|
||||||
|
title = Column(String(500), nullable=True)
|
||||||
|
|
||||||
|
# Cryptographic hashes
|
||||||
|
sha256 = Column(String(64), nullable=False, index=True)
|
||||||
|
sha512 = Column(String(128), nullable=False)
|
||||||
|
keccak256 = Column(String(64), nullable=False)
|
||||||
|
|
||||||
|
# Metadata
|
||||||
|
canonical_size = Column(Integer, nullable=False)
|
||||||
|
|
||||||
|
# Blockchain anchoring
|
||||||
|
anchor_status = Column(
|
||||||
|
String(20),
|
||||||
|
nullable=False,
|
||||||
|
default="pending"
|
||||||
|
) # pending, anchored, failed
|
||||||
|
anchor_txid = Column(String(128), nullable=True)
|
||||||
|
anchor_chain = Column(String(50), nullable=True)
|
||||||
|
anchor_block_height = Column(Integer, nullable=True)
|
||||||
|
anchored_at = Column(DateTime(timezone=True), nullable=True)
|
||||||
|
|
||||||
|
# Timestamps
|
||||||
|
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
||||||
|
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<LEO {self.id} by {self.author}>"
|
||||||
|
|
||||||
|
|
||||||
|
class AnchorEvent(Base):
|
||||||
|
"""
|
||||||
|
Blockchain anchor event audit trail
|
||||||
|
|
||||||
|
Tracks all anchoring attempts and status changes for LEOs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "anchor_events"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
leo_id = Column(String(36), nullable=False, index=True)
|
||||||
|
|
||||||
|
# Event details
|
||||||
|
event_type = Column(String(50), nullable=False) # anchor_initiated, anchor_confirmed, anchor_failed
|
||||||
|
chain = Column(String(50), nullable=True)
|
||||||
|
txid = Column(String(128), nullable=True)
|
||||||
|
block_height = Column(Integer, nullable=True)
|
||||||
|
|
||||||
|
# Status
|
||||||
|
status = Column(String(20), nullable=False) # pending, confirmed, failed
|
||||||
|
error_message = Column(Text, nullable=True)
|
||||||
|
|
||||||
|
# Metadata
|
||||||
|
metadata = Column(Text, nullable=True) # JSON serialized
|
||||||
|
|
||||||
|
# Timestamps
|
||||||
|
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<AnchorEvent {self.id} for LEO {self.leo_id}>"
|
||||||
317
backend/app/routers/ip_vault.py
Normal file
317
backend/app/routers/ip_vault.py
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
"""IP Vault routes - Cryptographic proof-of-origin for ideas"""
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy import select, desc, func
|
||||||
|
from typing import List, Optional
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from app.database import get_db
|
||||||
|
from app.models.leo import LEO as LEOModel, AnchorEvent
|
||||||
|
from app.schemas.leo import (
|
||||||
|
LEOCreate,
|
||||||
|
LEOResponse,
|
||||||
|
LEODetail,
|
||||||
|
LEOList,
|
||||||
|
AnchorRequest,
|
||||||
|
AnchorEventResponse
|
||||||
|
)
|
||||||
|
|
||||||
|
# Import the VaultAgent
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../")))
|
||||||
|
from agents.categories.security.vault_agent import VaultAgent
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/api/vault", tags=["IP Vault"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/leos", response_model=LEODetail, status_code=status.HTTP_201_CREATED)
|
||||||
|
async def create_leo(
|
||||||
|
leo_data: LEOCreate,
|
||||||
|
db: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Create a new Ledger Evidence Object (LEO).
|
||||||
|
|
||||||
|
Vaults an idea by:
|
||||||
|
1. Canonicalizing the text
|
||||||
|
2. Computing cryptographic hashes
|
||||||
|
3. Creating a LEO record
|
||||||
|
4. Returning verification and anchoring instructions
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Initialize VaultAgent
|
||||||
|
agent = VaultAgent()
|
||||||
|
|
||||||
|
# Create LEO using agent
|
||||||
|
result = await agent.run({
|
||||||
|
'action': 'create_leo',
|
||||||
|
'idea': leo_data.idea,
|
||||||
|
'author': leo_data.author,
|
||||||
|
'title': leo_data.title
|
||||||
|
})
|
||||||
|
|
||||||
|
if result.status.value != 'completed':
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"Failed to create LEO: {result.error}"
|
||||||
|
)
|
||||||
|
|
||||||
|
leo_dict = result.data['result']['leo']
|
||||||
|
verification_text = result.data['result']['verification_text']
|
||||||
|
anchoring_options = result.data['result']['anchoring_options']
|
||||||
|
|
||||||
|
# Create database record
|
||||||
|
db_leo = LEOModel(
|
||||||
|
id=leo_dict['id'],
|
||||||
|
author=leo_dict['author'],
|
||||||
|
title=leo_dict['title'],
|
||||||
|
sha256=leo_dict['sha256'],
|
||||||
|
sha512=leo_dict['sha512'],
|
||||||
|
keccak256=leo_dict['keccak256'],
|
||||||
|
canonical_size=leo_dict['canonical_size'],
|
||||||
|
anchor_status=leo_dict['anchor_status'],
|
||||||
|
created_at=datetime.fromisoformat(leo_dict['created_at'].replace('Z', '+00:00'))
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(db_leo)
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(db_leo)
|
||||||
|
|
||||||
|
# Return detailed response
|
||||||
|
return LEODetail(
|
||||||
|
id=db_leo.id,
|
||||||
|
author=db_leo.author,
|
||||||
|
title=db_leo.title,
|
||||||
|
sha256=db_leo.sha256,
|
||||||
|
sha512=db_leo.sha512,
|
||||||
|
keccak256=db_leo.keccak256,
|
||||||
|
canonical_size=db_leo.canonical_size,
|
||||||
|
anchor_status=db_leo.anchor_status,
|
||||||
|
anchor_txid=db_leo.anchor_txid,
|
||||||
|
anchor_chain=db_leo.anchor_chain,
|
||||||
|
anchor_block_height=db_leo.anchor_block_height,
|
||||||
|
anchored_at=db_leo.anchored_at,
|
||||||
|
created_at=db_leo.created_at,
|
||||||
|
updated_at=db_leo.updated_at,
|
||||||
|
verification_text=verification_text,
|
||||||
|
anchoring_options=anchoring_options
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await db.rollback()
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"Error creating LEO: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/leos", response_model=LEOList)
|
||||||
|
async def list_leos(
|
||||||
|
page: int = 1,
|
||||||
|
per_page: int = 20,
|
||||||
|
author: Optional[str] = None,
|
||||||
|
db: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
List all LEOs with pagination.
|
||||||
|
|
||||||
|
Query parameters:
|
||||||
|
- page: Page number (default: 1)
|
||||||
|
- per_page: Items per page (default: 20, max: 100)
|
||||||
|
- author: Filter by author (optional)
|
||||||
|
"""
|
||||||
|
# Validate pagination
|
||||||
|
if page < 1:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Page must be >= 1"
|
||||||
|
)
|
||||||
|
if per_page < 1 or per_page > 100:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="per_page must be between 1 and 100"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build query
|
||||||
|
query = select(LEOModel)
|
||||||
|
|
||||||
|
# Filter by author if provided
|
||||||
|
if author:
|
||||||
|
query = query.where(LEOModel.author == author)
|
||||||
|
|
||||||
|
# Order by created_at descending (newest first)
|
||||||
|
query = query.order_by(desc(LEOModel.created_at))
|
||||||
|
|
||||||
|
# Count total
|
||||||
|
count_query = select(func.count()).select_from(LEOModel)
|
||||||
|
if author:
|
||||||
|
count_query = count_query.where(LEOModel.author == author)
|
||||||
|
total_result = await db.execute(count_query)
|
||||||
|
total = total_result.scalar()
|
||||||
|
|
||||||
|
# Apply pagination
|
||||||
|
offset = (page - 1) * per_page
|
||||||
|
query = query.offset(offset).limit(per_page)
|
||||||
|
|
||||||
|
# Execute query
|
||||||
|
result = await db.execute(query)
|
||||||
|
leos = result.scalars().all()
|
||||||
|
|
||||||
|
return LEOList(
|
||||||
|
leos=[LEOResponse.model_validate(leo) for leo in leos],
|
||||||
|
total=total,
|
||||||
|
page=page,
|
||||||
|
per_page=per_page
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/leos/{leo_id}", response_model=LEODetail)
|
||||||
|
async def get_leo(
|
||||||
|
leo_id: str,
|
||||||
|
db: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Get a specific LEO by ID.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- Full LEO details
|
||||||
|
- Verification instructions
|
||||||
|
- Anchoring options
|
||||||
|
"""
|
||||||
|
# Query database
|
||||||
|
result = await db.execute(
|
||||||
|
select(LEOModel).where(LEOModel.id == leo_id)
|
||||||
|
)
|
||||||
|
leo = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if not leo:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail=f"LEO with ID {leo_id} not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate verification text and anchoring options
|
||||||
|
agent = VaultAgent()
|
||||||
|
|
||||||
|
# Reconstruct LEO for verification text
|
||||||
|
from agents.categories.security.vault_agent import LEO as LEODataclass
|
||||||
|
leo_obj = LEODataclass(
|
||||||
|
id=leo.id,
|
||||||
|
author=leo.author,
|
||||||
|
title=leo.title,
|
||||||
|
canonical_size=leo.canonical_size,
|
||||||
|
sha256=leo.sha256,
|
||||||
|
sha512=leo.sha512,
|
||||||
|
keccak256=leo.keccak256,
|
||||||
|
created_at=leo.created_at.isoformat() + 'Z',
|
||||||
|
anchor_status=leo.anchor_status,
|
||||||
|
anchor_txid=leo.anchor_txid,
|
||||||
|
anchor_chain=leo.anchor_chain
|
||||||
|
)
|
||||||
|
|
||||||
|
verification_text = agent._generate_verification_text(leo_obj)
|
||||||
|
anchoring_options = agent._generate_anchoring_options(leo_obj)
|
||||||
|
|
||||||
|
return LEODetail(
|
||||||
|
id=leo.id,
|
||||||
|
author=leo.author,
|
||||||
|
title=leo.title,
|
||||||
|
sha256=leo.sha256,
|
||||||
|
sha512=leo.sha512,
|
||||||
|
keccak256=leo.keccak256,
|
||||||
|
canonical_size=leo.canonical_size,
|
||||||
|
anchor_status=leo.anchor_status,
|
||||||
|
anchor_txid=leo.anchor_txid,
|
||||||
|
anchor_chain=leo.anchor_chain,
|
||||||
|
anchor_block_height=leo.anchor_block_height,
|
||||||
|
anchored_at=leo.anchored_at,
|
||||||
|
created_at=leo.created_at,
|
||||||
|
updated_at=leo.updated_at,
|
||||||
|
verification_text=verification_text,
|
||||||
|
anchoring_options=anchoring_options
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/leos/{leo_id}/anchor", response_model=LEOResponse)
|
||||||
|
async def anchor_leo(
|
||||||
|
leo_id: str,
|
||||||
|
anchor_data: AnchorRequest,
|
||||||
|
db: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Initiate blockchain anchoring for a LEO.
|
||||||
|
|
||||||
|
NOTE: This is a placeholder endpoint for v0.
|
||||||
|
Future implementation will integrate with actual blockchain networks.
|
||||||
|
"""
|
||||||
|
# Query LEO
|
||||||
|
result = await db.execute(
|
||||||
|
select(LEOModel).where(LEOModel.id == leo_id)
|
||||||
|
)
|
||||||
|
leo = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if not leo:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail=f"LEO with ID {leo_id} not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if already anchored
|
||||||
|
if leo.anchor_status == 'anchored':
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="LEO is already anchored"
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO: Implement actual blockchain anchoring
|
||||||
|
# For v0, just update status to indicate anchoring initiated
|
||||||
|
leo.anchor_status = 'pending'
|
||||||
|
leo.anchor_chain = anchor_data.chain
|
||||||
|
|
||||||
|
# Create anchor event
|
||||||
|
anchor_event = AnchorEvent(
|
||||||
|
leo_id=leo_id,
|
||||||
|
event_type='anchor_initiated',
|
||||||
|
chain=anchor_data.chain,
|
||||||
|
status='pending'
|
||||||
|
)
|
||||||
|
db.add(anchor_event)
|
||||||
|
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(leo)
|
||||||
|
|
||||||
|
return LEOResponse.model_validate(leo)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/leos/{leo_id}/events", response_model=List[AnchorEventResponse])
|
||||||
|
async def get_leo_events(
|
||||||
|
leo_id: str,
|
||||||
|
db: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Get all anchor events for a LEO.
|
||||||
|
|
||||||
|
Returns the audit trail of all anchoring attempts and status changes.
|
||||||
|
"""
|
||||||
|
# Verify LEO exists
|
||||||
|
leo_result = await db.execute(
|
||||||
|
select(LEOModel).where(LEOModel.id == leo_id)
|
||||||
|
)
|
||||||
|
leo = leo_result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if not leo:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail=f"LEO with ID {leo_id} not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Query events
|
||||||
|
result = await db.execute(
|
||||||
|
select(AnchorEvent)
|
||||||
|
.where(AnchorEvent.leo_id == leo_id)
|
||||||
|
.order_by(desc(AnchorEvent.created_at))
|
||||||
|
)
|
||||||
|
events = result.scalars().all()
|
||||||
|
|
||||||
|
return [AnchorEventResponse.model_validate(event) for event in events]
|
||||||
@@ -1 +1,19 @@
|
|||||||
"""Pydantic schemas for request/response validation"""
|
"""Pydantic schemas for request/response validation"""
|
||||||
|
|
||||||
|
from app.schemas.leo import (
|
||||||
|
LEOCreate,
|
||||||
|
LEOResponse,
|
||||||
|
LEODetail,
|
||||||
|
LEOList,
|
||||||
|
AnchorRequest,
|
||||||
|
AnchorEventResponse
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"LEOCreate",
|
||||||
|
"LEOResponse",
|
||||||
|
"LEODetail",
|
||||||
|
"LEOList",
|
||||||
|
"AnchorRequest",
|
||||||
|
"AnchorEventResponse",
|
||||||
|
]
|
||||||
|
|||||||
72
backend/app/schemas/leo.py
Normal file
72
backend/app/schemas/leo.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
"""LEO (Ledger Evidence Object) schemas"""
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class LEOCreate(BaseModel):
|
||||||
|
"""LEO creation schema"""
|
||||||
|
idea: str = Field(..., min_length=1, description="Raw idea text to vault")
|
||||||
|
author: str = Field(default="Alexa", description="Author of the idea")
|
||||||
|
title: Optional[str] = Field(None, max_length=500, description="Optional title for the idea")
|
||||||
|
|
||||||
|
|
||||||
|
class LEOResponse(BaseModel):
|
||||||
|
"""LEO response schema"""
|
||||||
|
id: str
|
||||||
|
author: str
|
||||||
|
title: Optional[str]
|
||||||
|
sha256: str
|
||||||
|
sha512: str
|
||||||
|
keccak256: str
|
||||||
|
canonical_size: int
|
||||||
|
anchor_status: str
|
||||||
|
anchor_txid: Optional[str]
|
||||||
|
anchor_chain: Optional[str]
|
||||||
|
anchor_block_height: Optional[int]
|
||||||
|
anchored_at: Optional[datetime]
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: Optional[datetime]
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class LEODetail(LEOResponse):
|
||||||
|
"""
|
||||||
|
Detailed LEO response with verification and anchoring info
|
||||||
|
"""
|
||||||
|
verification_text: str
|
||||||
|
anchoring_options: Dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
|
class LEOList(BaseModel):
|
||||||
|
"""Paginated LEO list response"""
|
||||||
|
leos: list[LEOResponse]
|
||||||
|
total: int
|
||||||
|
page: int
|
||||||
|
per_page: int
|
||||||
|
|
||||||
|
|
||||||
|
class AnchorRequest(BaseModel):
|
||||||
|
"""Blockchain anchor request"""
|
||||||
|
chain: str = Field(
|
||||||
|
default="bitcoin",
|
||||||
|
description="Target blockchain (bitcoin, litecoin, ethereum)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AnchorEventResponse(BaseModel):
|
||||||
|
"""Anchor event response"""
|
||||||
|
id: int
|
||||||
|
leo_id: str
|
||||||
|
event_type: str
|
||||||
|
chain: Optional[str]
|
||||||
|
txid: Optional[str]
|
||||||
|
block_height: Optional[int]
|
||||||
|
status: str
|
||||||
|
error_message: Optional[str]
|
||||||
|
created_at: datetime
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
@@ -1125,6 +1125,10 @@
|
|||||||
<div class="icon-image">🤖</div>
|
<div class="icon-image">🤖</div>
|
||||||
<div class="icon-label">AI Assistant</div>
|
<div class="icon-label">AI Assistant</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="icon" ondblclick="openWindow('ip-vault')">
|
||||||
|
<div class="icon-image">🔐</div>
|
||||||
|
<div class="icon-label">IP Vault</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- RoadMail Window -->
|
<!-- RoadMail Window -->
|
||||||
@@ -2041,6 +2045,22 @@
|
|||||||
|
|
||||||
<div id="wallet" class="window" style="left: 200px; top: 170px; width: 500px; height: 350px;"><div class="title-bar" onmousedown="dragStart(event, 'wallet')"><div class="title-text"><span>💰</span><span>RoadCoin Wallet</span></div><div class="title-buttons"><div class="title-button" onclick="minimizeWindow('wallet')">_</div><div class="title-button" onclick="maximizeWindow('wallet')">□</div><div class="title-button" onclick="closeWindow('wallet')">×</div></div></div><div class="window-content" style="padding: 20px;"><h2 style="text-align: center; margin: 20px 0;">1,247.89 RC</h2><div style="text-align: center; color: #666; font-size: 11px;">≈ $18,705 USD</div></div></div>
|
<div id="wallet" class="window" style="left: 200px; top: 170px; width: 500px; height: 350px;"><div class="title-bar" onmousedown="dragStart(event, 'wallet')"><div class="title-text"><span>💰</span><span>RoadCoin Wallet</span></div><div class="title-buttons"><div class="title-button" onclick="minimizeWindow('wallet')">_</div><div class="title-button" onclick="maximizeWindow('wallet')">□</div><div class="title-button" onclick="closeWindow('wallet')">×</div></div></div><div class="window-content" style="padding: 20px;"><h2 style="text-align: center; margin: 20px 0;">1,247.89 RC</h2><div style="text-align: center; color: #666; font-size: 11px;">≈ $18,705 USD</div></div></div>
|
||||||
|
|
||||||
|
<!-- IP Vault Window -->
|
||||||
|
<div id="ip-vault" class="window" style="left: 220px; top: 190px; width: 700px; height: 600px;">
|
||||||
|
<div class="title-bar" onmousedown="dragStart(event, 'ip-vault')">
|
||||||
|
<div class="title-text">
|
||||||
|
<span>🔐</span>
|
||||||
|
<span>IP Vault - Cryptographic Proof of Origin</span>
|
||||||
|
</div>
|
||||||
|
<div class="title-buttons">
|
||||||
|
<div class="title-button" onclick="minimizeWindow('ip-vault')">_</div>
|
||||||
|
<div class="title-button" onclick="maximizeWindow('ip-vault')">□</div>
|
||||||
|
<div class="title-button" onclick="closeWindow('ip-vault')">×</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="window-content"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Taskbar -->
|
<!-- Taskbar -->
|
||||||
<div class="taskbar">
|
<div class="taskbar">
|
||||||
<div class="start-button" onclick="toggleStartMenu()">
|
<div class="start-button" onclick="toggleStartMenu()">
|
||||||
@@ -2076,6 +2096,8 @@
|
|||||||
<div class="start-menu-item" onclick="openWindow('road-craft'); toggleStartMenu();"><span style="font-size: 18px;">⛏️</span><span>RoadCraft</span></div>
|
<div class="start-menu-item" onclick="openWindow('road-craft'); toggleStartMenu();"><span style="font-size: 18px;">⛏️</span><span>RoadCraft</span></div>
|
||||||
<div class="start-menu-item" onclick="openWindow('road-life'); toggleStartMenu();"><span style="font-size: 18px;">🏡</span><span>Road Life</span></div>
|
<div class="start-menu-item" onclick="openWindow('road-life'); toggleStartMenu();"><span style="font-size: 18px;">🏡</span><span>Road Life</span></div>
|
||||||
<div class="start-menu-separator"></div>
|
<div class="start-menu-separator"></div>
|
||||||
|
<div class="start-menu-item" onclick="openWindow('ip-vault'); toggleStartMenu();"><span style="font-size: 18px;">🔐</span><span>IP Vault</span></div>
|
||||||
|
<div class="start-menu-separator"></div>
|
||||||
<div class="start-menu-item" onclick="alert('Shutting down...\\n\\nJust kidding! The road never ends! 🛣️')"><span style="font-size: 18px;">🔌</span><span>Shut Down</span></div>
|
<div class="start-menu-item" onclick="alert('Shutting down...\\n\\nJust kidding! The road never ends! 🛣️')"><span style="font-size: 18px;">🔌</span><span>Shut Down</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -419,6 +419,38 @@ class ApiClient {
|
|||||||
async deleteFile(fileId) {
|
async deleteFile(fileId) {
|
||||||
return this.delete(`/api/files/${fileId}`);
|
return this.delete(`/api/files/${fileId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== IP Vault API =====
|
||||||
|
|
||||||
|
async createLEO(idea, author = 'Alexa', title = null) {
|
||||||
|
return this.post('/api/vault/leos', {
|
||||||
|
idea,
|
||||||
|
author,
|
||||||
|
title
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLEOs(page = 1, perPage = 20, author = null) {
|
||||||
|
let endpoint = `/api/vault/leos?page=${page}&per_page=${perPage}`;
|
||||||
|
if (author) {
|
||||||
|
endpoint += `&author=${encodeURIComponent(author)}`;
|
||||||
|
}
|
||||||
|
return this.get(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLEO(leoId) {
|
||||||
|
return this.get(`/api/vault/leos/${leoId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async anchorLEO(leoId, chain = 'bitcoin') {
|
||||||
|
return this.post(`/api/vault/leos/${leoId}/anchor`, {
|
||||||
|
chain
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLEOEvents(leoId) {
|
||||||
|
return this.get(`/api/vault/leos/${leoId}/events`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create singleton instance
|
// Create singleton instance
|
||||||
|
|||||||
@@ -91,6 +91,9 @@ class BlackRoadApps {
|
|||||||
case 'ai-chat':
|
case 'ai-chat':
|
||||||
this.loadAIChat();
|
this.loadAIChat();
|
||||||
break;
|
break;
|
||||||
|
case 'ip-vault':
|
||||||
|
this.loadIPVault();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -819,6 +822,184 @@ class BlackRoadApps {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== IP VAULT APPLICATION =====
|
||||||
|
|
||||||
|
async loadIPVault() {
|
||||||
|
try {
|
||||||
|
const response = await this.api.getLEOs(1, 20);
|
||||||
|
this.updateIPVaultUI(response);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load IP Vault:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateIPVaultUI(response) {
|
||||||
|
const content = document.querySelector('#ip-vault .window-content');
|
||||||
|
if (!content) return;
|
||||||
|
|
||||||
|
const { leos, total, page, per_page } = response;
|
||||||
|
|
||||||
|
content.innerHTML = `
|
||||||
|
<div class="ip-vault-container" style="padding: 20px;">
|
||||||
|
<div class="vault-header" style="margin-bottom: 20px;">
|
||||||
|
<h2 style="margin: 0 0 10px 0;">🔐 IP Vault</h2>
|
||||||
|
<p style="color: #666; font-size: 12px; margin: 0;">Cryptographic proof-of-origin for your ideas</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="vault-form" style="background: #f5f5f5; padding: 15px; margin-bottom: 20px; border-radius: 4px;">
|
||||||
|
<div style="margin-bottom: 10px;">
|
||||||
|
<label style="display: block; margin-bottom: 5px; font-weight: bold;">Title (optional):</label>
|
||||||
|
<input type="text" id="vault-title-input"
|
||||||
|
style="width: 100%; padding: 8px; border: 1px solid #ccc; font-family: 'MS Sans Serif', Arial, sans-serif;"
|
||||||
|
placeholder="e.g. Quantum ledger architecture">
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 10px;">
|
||||||
|
<label style="display: block; margin-bottom: 5px; font-weight: bold;">Idea:</label>
|
||||||
|
<textarea id="vault-idea-input"
|
||||||
|
style="width: 100%; height: 100px; padding: 8px; border: 1px solid #ccc; font-family: 'MS Sans Serif', Arial, sans-serif; resize: vertical;"
|
||||||
|
placeholder="Enter your idea or concept here..."></textarea>
|
||||||
|
</div>
|
||||||
|
<button onclick="window.BlackRoadApps.vaultIdea()"
|
||||||
|
style="padding: 8px 20px; background: #0078d7; color: white; border: none; cursor: pointer; font-weight: bold;">
|
||||||
|
🔒 Vault This
|
||||||
|
</button>
|
||||||
|
<div id="vault-status" style="margin-top: 10px; color: #666; font-size: 12px;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="vault-list">
|
||||||
|
<h3 style="margin: 0 0 15px 0;">Recent Vaulted Ideas (${total} total)</h3>
|
||||||
|
<div class="leo-list" style="max-height: 300px; overflow-y: auto;">
|
||||||
|
${leos.length > 0 ? leos.map(leo => `
|
||||||
|
<div class="leo-item" style="background: white; border: 1px solid #ccc; padding: 12px; margin-bottom: 10px; cursor: pointer;"
|
||||||
|
onclick="window.BlackRoadApps.viewLEO('${leo.id}')">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 5px;">
|
||||||
|
${this.escapeHtml(leo.title || 'Untitled')}
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 11px; color: #666; margin-bottom: 5px;">
|
||||||
|
by ${this.escapeHtml(leo.author)} • ${this.formatTime(leo.created_at)}
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 10px; font-family: monospace; color: #999;">
|
||||||
|
SHA-256: ${leo.sha256.substring(0, 32)}...
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 10px; margin-top: 5px;">
|
||||||
|
Status: <span style="color: ${leo.anchor_status === 'anchored' ? '#2ecc40' : '#ff851b'}; font-weight: bold;">
|
||||||
|
${leo.anchor_status.toUpperCase()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('') : '<div style="color: #666; text-align: center; padding: 40px;">No vaulted ideas yet. Create your first one above!</div>'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async vaultIdea() {
|
||||||
|
const titleInput = document.getElementById('vault-title-input');
|
||||||
|
const ideaInput = document.getElementById('vault-idea-input');
|
||||||
|
const statusDiv = document.getElementById('vault-status');
|
||||||
|
|
||||||
|
const title = titleInput?.value.trim() || null;
|
||||||
|
const idea = ideaInput?.value.trim();
|
||||||
|
|
||||||
|
if (!idea) {
|
||||||
|
if (statusDiv) statusDiv.innerHTML = '<span style="color: #d9534f;">Please enter an idea to vault.</span>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (statusDiv) statusDiv.innerHTML = '<span style="color: #0078d7;">Vaulting...</span>';
|
||||||
|
|
||||||
|
const leo = await this.api.createLEO(idea, 'Alexa', title);
|
||||||
|
|
||||||
|
if (statusDiv) {
|
||||||
|
statusDiv.innerHTML = `<span style="color: #2ecc40;">✓ Vaulted successfully! LEO ID: ${leo.id.substring(0, 8)}...</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear inputs
|
||||||
|
if (titleInput) titleInput.value = '';
|
||||||
|
if (ideaInput) ideaInput.value = '';
|
||||||
|
|
||||||
|
// Reload the list
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loadIPVault();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to vault idea:', error);
|
||||||
|
if (statusDiv) {
|
||||||
|
statusDiv.innerHTML = `<span style="color: #d9534f;">Failed to vault: ${this.escapeHtml(error.message || 'Unknown error')}</span>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async viewLEO(leoId) {
|
||||||
|
try {
|
||||||
|
const leo = await this.api.getLEO(leoId);
|
||||||
|
|
||||||
|
// Create a modal/dialog to show LEO details
|
||||||
|
const modal = document.createElement('div');
|
||||||
|
modal.style.cssText = 'position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 10000;';
|
||||||
|
modal.onclick = () => modal.remove();
|
||||||
|
|
||||||
|
const dialog = document.createElement('div');
|
||||||
|
dialog.style.cssText = 'background: white; padding: 20px; max-width: 600px; max-height: 80vh; overflow-y: auto; border: 2px solid #0078d7;';
|
||||||
|
dialog.onclick = (e) => e.stopPropagation();
|
||||||
|
|
||||||
|
dialog.innerHTML = `
|
||||||
|
<h2 style="margin: 0 0 15px 0;">🔐 LEO Details</h2>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 20px;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 5px;">Title:</div>
|
||||||
|
<div style="margin-bottom: 15px;">${this.escapeHtml(leo.title || 'Untitled')}</div>
|
||||||
|
|
||||||
|
<div style="font-weight: bold; margin-bottom: 5px;">Author:</div>
|
||||||
|
<div style="margin-bottom: 15px;">${this.escapeHtml(leo.author)}</div>
|
||||||
|
|
||||||
|
<div style="font-weight: bold; margin-bottom: 5px;">Created:</div>
|
||||||
|
<div style="margin-bottom: 15px;">${new Date(leo.created_at).toLocaleString()}</div>
|
||||||
|
|
||||||
|
<div style="font-weight: bold; margin-bottom: 5px;">LEO ID:</div>
|
||||||
|
<div style="font-family: monospace; font-size: 11px; background: #f5f5f5; padding: 5px; margin-bottom: 15px;">${leo.id}</div>
|
||||||
|
|
||||||
|
<div style="font-weight: bold; margin-bottom: 5px;">SHA-256:</div>
|
||||||
|
<div style="font-family: monospace; font-size: 10px; background: #f5f5f5; padding: 5px; word-break: break-all; margin-bottom: 15px;">${leo.sha256}</div>
|
||||||
|
|
||||||
|
<div style="font-weight: bold; margin-bottom: 5px;">SHA-512:</div>
|
||||||
|
<div style="font-family: monospace; font-size: 10px; background: #f5f5f5; padding: 5px; word-break: break-all; margin-bottom: 15px;">${leo.sha512}</div>
|
||||||
|
|
||||||
|
<div style="font-weight: bold; margin-bottom: 5px;">Keccak-256 (Ethereum-compatible):</div>
|
||||||
|
<div style="font-family: monospace; font-size: 10px; background: #f5f5f5; padding: 5px; word-break: break-all; margin-bottom: 15px;">${leo.keccak256}</div>
|
||||||
|
|
||||||
|
<div style="font-weight: bold; margin-bottom: 5px;">Anchor Status:</div>
|
||||||
|
<div style="margin-bottom: 15px;">
|
||||||
|
<span style="color: ${leo.anchor_status === 'anchored' ? '#2ecc40' : '#ff851b'}; font-weight: bold;">
|
||||||
|
${leo.anchor_status.toUpperCase()}
|
||||||
|
</span>
|
||||||
|
${leo.anchor_txid ? `<br><span style="font-size: 11px;">TX: ${leo.anchor_txid}</span>` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 20px; padding: 10px; background: #fffef0; border-left: 3px solid #ffdc00;">
|
||||||
|
<div style="font-weight: bold; margin-bottom: 5px;">Verification Instructions:</div>
|
||||||
|
<div style="font-size: 11px; white-space: pre-wrap;">${this.escapeHtml(leo.verification_text)}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button onclick="this.parentElement.parentElement.remove()"
|
||||||
|
style="padding: 8px 20px; background: #0078d7; color: white; border: none; cursor: pointer; font-weight: bold;">
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
modal.appendChild(dialog);
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load LEO details:', error);
|
||||||
|
alert('Failed to load LEO details: ' + (error.message || 'Unknown error'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ===== UTILITY FUNCTIONS =====
|
// ===== UTILITY FUNCTIONS =====
|
||||||
|
|
||||||
escapeHtml(value) {
|
escapeHtml(value) {
|
||||||
|
|||||||
Reference in New Issue
Block a user