Deployment Guide¶
This guide covers deploying Mistaber in production environments, from containerized deployments to cloud platforms and CI/CD integration.
Prerequisites¶
Before deploying, ensure you have:
- Python 3.10+ available in your target environment
- Clingo 5.6.0+ ASP solver
- Access to the ontology files
- Sufficient memory (2GB minimum recommended)
Docker Deployment¶
Dockerfile for Mistaber¶
Create a production-ready Dockerfile:
# Dockerfile
FROM python:3.11-slim-bookworm
# Install system dependencies for clingo
RUN apt-get update && apt-get install -y --no-install-recommends \
gringo \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Set working directory
WORKDIR /app
# Copy dependency files first for better layer caching
COPY pyproject.toml ./
COPY README.md ./
# Install Python dependencies
RUN pip install --no-cache-dir clingo click lark pyyaml
# Copy application code
COPY mistaber/ ./mistaber/
# Install mistaber package
RUN pip install --no-cache-dir -e .
# Create non-root user for security
RUN useradd --create-home --shell /bin/bash mistaber && \
chown -R mistaber:mistaber /app
USER mistaber
# Set default ontology path
ENV MISTABER_ONTOLOGY=/app/mistaber/ontology
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "from mistaber.engine import HsrsEngine; print('healthy')" || exit 1
# Default command
ENTRYPOINT ["mistaber"]
CMD ["--help"]
Multi-Stage Build for Smaller Images¶
For production deployments where image size matters:
# Dockerfile.production
# Build stage
FROM python:3.11-slim-bookworm AS builder
RUN apt-get update && apt-get install -y --no-install-recommends \
gringo \
build-essential
WORKDIR /build
COPY pyproject.toml README.md ./
COPY mistaber/ ./mistaber/
RUN pip install --no-cache-dir build && \
python -m build --wheel
# Runtime stage
FROM python:3.11-slim-bookworm AS runtime
# Install only runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gringo \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copy wheel from builder
COPY --from=builder /build/dist/*.whl ./
# Install the package
RUN pip install --no-cache-dir *.whl clingo && rm *.whl
# Copy ontology separately (can be mounted as volume)
COPY mistaber/ontology/ ./ontology/
# Non-root user
RUN useradd --create-home mistaber && chown -R mistaber:mistaber /app
USER mistaber
ENV MISTABER_ONTOLOGY=/app/ontology
ENTRYPOINT ["mistaber"]
Docker Compose Setup¶
For development and testing with multiple services:
# docker-compose.yml
version: '3.8'
services:
mistaber:
build:
context: .
dockerfile: Dockerfile
image: mistaber:latest
container_name: mistaber-engine
volumes:
# Mount ontology for live updates during development
- ./mistaber/ontology:/app/mistaber/ontology:ro
# Mount custom extensions
- ./extensions:/app/extensions:ro
environment:
- MISTABER_ONTOLOGY=/app/mistaber/ontology
- MISTABER_FORMAT=json
# Keep container running for interactive use
command: ["tail", "-f", "/dev/null"]
healthcheck:
test: ["CMD", "python", "-c", "from mistaber.engine import HsrsEngine; print('ok')"]
interval: 30s
timeout: 10s
retries: 3
deploy:
resources:
limits:
memory: 2G
reservations:
memory: 512M
# Optional: API wrapper service
mistaber-api:
build:
context: .
dockerfile: Dockerfile.api
image: mistaber-api:latest
container_name: mistaber-api
ports:
- "8080:8080"
environment:
- MISTABER_ONTOLOGY=/app/mistaber/ontology
depends_on:
mistaber:
condition: service_healthy
volumes:
- ./mistaber/ontology:/app/mistaber/ontology:ro
# Optional: Redis for query caching
cache:
image: redis:7-alpine
container_name: mistaber-cache
ports:
- "6379:6379"
volumes:
- redis-data:/data
volumes:
redis-data:
Building and Running¶
# Build the image
docker build -t mistaber:latest .
# Run a query
docker run --rm mistaber:latest -o /app/mistaber/ontology query "world(X)"
# Run interactively
docker run -it --rm mistaber:latest /bin/bash
# Run with mounted ontology (for development)
docker run --rm -v $(pwd)/mistaber/ontology:/app/mistaber/ontology:ro \
mistaber:latest query "world(X)"
# Using Docker Compose
docker compose up -d
docker compose exec mistaber mistaber query "world(X)"
Volume Mounts for Ontology¶
For production deployments, mount the ontology as a read-only volume to enable updates without rebuilding:
# Production deployment with external ontology
docker run -d \
--name mistaber-prod \
-v /data/mistaber-ontology:/app/ontology:ro \
-e MISTABER_ONTOLOGY=/app/ontology \
mistaber:latest
Environment Variables¶
| Variable | Description | Default |
|---|---|---|
MISTABER_ONTOLOGY |
Path to ontology directory | ./mistaber/ontology |
MISTABER_FORMAT |
Default output format (text, json) |
text |
MISTABER_DEBUG |
Enable debug output (0, 1) |
0 |
MISTABER_CACHE_TTL |
Query cache TTL in seconds | 300 |
Cloud Deployment¶
AWS Deployment¶
EC2 Instance Setup¶
-
Launch an EC2 instance:
- AMI: Amazon Linux 2023 or Ubuntu 22.04 LTS
- Instance type: t3.medium (2 vCPU, 4GB RAM) minimum
- Storage: 20GB gp3 SSD
-
Install dependencies:
-
Install Mistaber:
-
Configure as a systemd service:
# /etc/systemd/system/mistaber.service [Unit] Description=Mistaber Halachic Reasoning Engine After=network.target [Service] Type=simple User=ubuntu WorkingDirectory=/home/ubuntu/mistaber Environment=PATH=/home/ubuntu/mistaber/venv/bin:/usr/local/bin:/usr/bin Environment=MISTABER_ONTOLOGY=/home/ubuntu/mistaber/mistaber/ontology ExecStart=/home/ubuntu/mistaber/venv/bin/python -m mistaber.api Restart=always RestartSec=10 [Install] WantedBy=multi-user.target
AWS Lambda for Serverless Queries¶
For serverless query execution:
# lambda_handler.py
import json
import os
from pathlib import Path
from mistaber.engine import HsrsEngine
# Initialize engine at module level for warm starts
ONTOLOGY_PATH = os.environ.get('ONTOLOGY_PATH', '/opt/ontology')
engine = None
def get_engine():
global engine
if engine is None:
engine = HsrsEngine(Path(ONTOLOGY_PATH))
return engine
def lambda_handler(event, context):
"""AWS Lambda handler for Mistaber queries."""
try:
body = json.loads(event.get('body', '{}'))
action = body.get('action', 'query')
pattern = body.get('pattern')
world = body.get('world')
eng = get_engine()
if action == 'query':
results = eng.query(pattern)
return {
'statusCode': 200,
'body': json.dumps({'results': results})
}
elif action == 'ask':
result = eng.ask(pattern, world=world)
return {
'statusCode': 200,
'body': json.dumps({'result': result})
}
elif action == 'compare':
worlds = body.get('worlds', [])
results = eng.compare(pattern, worlds=worlds if worlds else None)
return {
'statusCode': 200,
'body': json.dumps({'results': results})
}
else:
return {
'statusCode': 400,
'body': json.dumps({'error': f'Unknown action: {action}'})
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps({'error': str(e)})
}
Lambda Layer for Dependencies:
# Create a Lambda layer with dependencies
mkdir -p layer/python
pip install clingo click lark pyyaml -t layer/python/
cd layer
zip -r ../mistaber-deps-layer.zip .
# Upload ontology as separate layer
mkdir -p ontology-layer/opt
cp -r mistaber/ontology ontology-layer/opt/
cd ontology-layer
zip -r ../mistaber-ontology-layer.zip .
SAM Template:
# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
MistaberFunction:
Type: AWS::Serverless::Function
Properties:
Handler: lambda_handler.lambda_handler
Runtime: python3.11
CodeUri: ./lambda/
MemorySize: 1024
Timeout: 30
Environment:
Variables:
ONTOLOGY_PATH: /opt/ontology
Layers:
- !Ref MistaberDepsLayer
- !Ref MistaberOntologyLayer
Events:
Api:
Type: Api
Properties:
Path: /query
Method: post
MistaberDepsLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: mistaber-deps
ContentUri: ./mistaber-deps-layer.zip
CompatibleRuntimes:
- python3.11
MistaberOntologyLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: mistaber-ontology
ContentUri: ./mistaber-ontology-layer.zip
CompatibleRuntimes:
- python3.11
S3 for Ontology Storage¶
Store and version ontology files in S3:
# ontology_loader.py
import boto3
import tempfile
from pathlib import Path
def load_ontology_from_s3(bucket: str, prefix: str = 'ontology/') -> Path:
"""Download ontology from S3 to local temp directory."""
s3 = boto3.client('s3')
temp_dir = Path(tempfile.mkdtemp())
paginator = s3.get_paginator('list_objects_v2')
for page in paginator.paginate(Bucket=bucket, Prefix=prefix):
for obj in page.get('Contents', []):
key = obj['Key']
local_path = temp_dir / key.replace(prefix, '')
local_path.parent.mkdir(parents=True, exist_ok=True)
s3.download_file(bucket, key, str(local_path))
return temp_dir
GCP Deployment¶
Compute Engine Setup¶
# Create a VM
gcloud compute instances create mistaber-vm \
--zone=us-central1-a \
--machine-type=e2-medium \
--image-family=ubuntu-2204-lts \
--image-project=ubuntu-os-cloud \
--boot-disk-size=20GB
# SSH into the instance
gcloud compute ssh mistaber-vm --zone=us-central1-a
# Install dependencies (on the VM)
sudo apt update
sudo apt install python3.11 python3.11-venv python3-pip git gringo
# Clone and install
git clone https://github.com/BrainyBlaze/mistraber.git
cd mistaber
python3.11 -m venv venv
source venv/bin/activate
pip install -e .
Cloud Functions¶
# main.py - GCP Cloud Function
import json
from pathlib import Path
from mistaber.engine import HsrsEngine
# Global engine instance for function warm starts
_engine = None
def get_engine():
global _engine
if _engine is None:
_engine = HsrsEngine(Path('/workspace/ontology'))
return _engine
def mistaber_query(request):
"""HTTP Cloud Function for Mistaber queries."""
request_json = request.get_json(silent=True)
if not request_json:
return json.dumps({'error': 'No JSON body provided'}), 400
pattern = request_json.get('pattern')
if not pattern:
return json.dumps({'error': 'Missing pattern parameter'}), 400
try:
engine = get_engine()
results = engine.query(pattern)
return json.dumps({'results': results}), 200
except Exception as e:
return json.dumps({'error': str(e)}), 500
Deployment:
gcloud functions deploy mistaber-query \
--runtime python311 \
--trigger-http \
--allow-unauthenticated \
--memory 1024MB \
--timeout 30s \
--source .
Cloud Storage for Ontology¶
# Create bucket
gsutil mb gs://mistaber-ontology
# Upload ontology
gsutil -m cp -r mistaber/ontology gs://mistaber-ontology/
# Set up versioning
gsutil versioning set on gs://mistaber-ontology
Azure Deployment¶
VM Setup¶
# Create resource group
az group create --name mistaber-rg --location eastus
# Create VM
az vm create \
--resource-group mistaber-rg \
--name mistaber-vm \
--image Ubuntu2204 \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys
# Get public IP
az vm show -d -g mistaber-rg -n mistaber-vm --query publicIps -o tsv
# SSH and install
ssh azureuser@<public-ip>
# Follow Ubuntu installation steps from above
Azure Functions¶
# __init__.py - Azure Function
import json
import logging
import azure.functions as func
from pathlib import Path
from mistaber.engine import HsrsEngine
# Initialize engine
engine = HsrsEngine(Path('/home/site/wwwroot/ontology'))
def main(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Mistaber query function triggered')
try:
req_body = req.get_json()
pattern = req_body.get('pattern')
if not pattern:
return func.HttpResponse(
json.dumps({'error': 'Missing pattern'}),
status_code=400,
mimetype='application/json'
)
results = engine.query(pattern)
return func.HttpResponse(
json.dumps({'results': results}),
status_code=200,
mimetype='application/json'
)
except Exception as e:
return func.HttpResponse(
json.dumps({'error': str(e)}),
status_code=500,
mimetype='application/json'
)
Blob Storage for Ontology¶
# Create storage account
az storage account create \
--name mistaberontology \
--resource-group mistaber-rg \
--location eastus \
--sku Standard_LRS
# Create container
az storage container create \
--name ontology \
--account-name mistaberontology
# Upload ontology
az storage blob upload-batch \
--destination ontology \
--source ./mistaber/ontology \
--account-name mistaberontology
CI/CD Integration¶
GitHub Actions Workflow¶
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install ruff
run: pip install ruff
- name: Run linting
run: |
ruff check .
ruff format --check .
type-check:
name: Type Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install -e ".[dev]"
- name: Run mypy
run: mypy mistaber/ --ignore-missing-imports
test:
name: Test (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ['3.10', '3.11', '3.12']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install system dependencies
run: sudo apt-get install -y gringo
- name: Install dependencies
run: pip install -e ".[dev]"
- name: Run tests
run: pytest --tb=short -v
- name: Upload coverage
uses: codecov/codecov-action@v3
if: matrix.python-version == '3.11'
ontology-validation:
name: Validate Ontology
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install -e .
- name: Validate ontology loads
run: |
python -c "
from pathlib import Path
from mistaber.engine import HsrsEngine
engine = HsrsEngine(Path('mistaber/ontology'))
assert engine.is_loaded, 'Ontology failed to load'
worlds = engine.query('world(X)')
assert len(worlds) >= 7, f'Expected 7+ worlds, got {len(worlds)}'
print('Ontology validation passed')
"
- name: Check for UNSAT
run: |
clingo mistaber/ontology/worlds/*.lp mistaber/ontology/base/*.lp \
mistaber/ontology/schema/*.lp -n 1 --warn=none
build:
name: Build Package
runs-on: ubuntu-latest
needs: [lint, type-check, test, ontology-validation]
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install build tools
run: pip install build
- name: Build package
run: python -m build
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: dist
path: dist/
docker:
name: Build Docker Image
runs-on: ubuntu-latest
needs: [build]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
Pre-commit Hooks¶
Configure pre-commit to run checks locally before commits:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.9
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
hooks:
- id: mypy
additional_dependencies: [types-PyYAML]
args: [--ignore-missing-imports]
- repo: local
hooks:
- id: ontology-check
name: Validate Ontology
entry: python -c "from pathlib import Path; from mistaber.engine import HsrsEngine; HsrsEngine(Path('mistaber/ontology'))"
language: system
pass_filenames: false
files: \.lp$
- id: pytest-check
name: Run Tests
entry: pytest --tb=short -q
language: system
pass_filenames: false
stages: [push]
Installation:
Documentation Deployment¶
# .github/workflows/docs.yml
name: Documentation
on:
push:
branches: [main]
paths:
- 'docs/**'
- 'mkdocs.yml'
- 'mistaber/**'
workflow_dispatch:
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install -e .
pip install -r docs-requirements.txt
- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Build and deploy
run: mkdocs gh-deploy --force
Performance Tuning¶
Grounding Optimization¶
The grounding phase can be the bottleneck for large ontologies:
% Use projection to reduce output size
#project asserts/2.
#project holds/2.
#project override/3.
% Add domain constraints early
:- asserts(W, P), not world(W).
:- asserts(W, issur(A, F, L)), not action(A).
:- asserts(W, issur(A, F, L)), not food(F).
:- asserts(W, issur(A, F, L)), not madrega(L).
Memory Management¶
# Reuse engine instances
from pathlib import Path
from mistaber.engine import HsrsEngine
class MistaberService:
_engine = None
@classmethod
def get_engine(cls) -> HsrsEngine:
if cls._engine is None:
cls._engine = HsrsEngine(Path("mistaber/ontology"))
return cls._engine
@classmethod
def reset_engine(cls):
"""Force reload of ontology (use sparingly)."""
cls._engine = None
Query Caching¶
Implement caching for frequently repeated queries:
import hashlib
import json
from functools import lru_cache
from typing import Optional
import redis
class CachedEngine:
def __init__(self, engine, redis_client: Optional[redis.Redis] = None):
self.engine = engine
self.redis = redis_client
self.cache_ttl = 300 # 5 minutes
def _cache_key(self, method: str, *args, **kwargs) -> str:
data = json.dumps({'method': method, 'args': args, 'kwargs': kwargs}, sort_keys=True)
return f"mistaber:{hashlib.md5(data.encode()).hexdigest()}"
def query(self, pattern: str) -> list:
if self.redis:
key = self._cache_key('query', pattern)
cached = self.redis.get(key)
if cached:
return json.loads(cached)
result = self.engine.query(pattern)
if self.redis:
self.redis.setex(key, self.cache_ttl, json.dumps(result))
return result
def invalidate_cache(self):
"""Clear all cached queries."""
if self.redis:
for key in self.redis.scan_iter("mistaber:*"):
self.redis.delete(key)
Parallel Processing¶
For batch queries, use parallel processing:
from concurrent.futures import ProcessPoolExecutor, as_completed
from pathlib import Path
def query_worker(args):
pattern, ontology_path = args
from mistaber.engine import HsrsEngine
engine = HsrsEngine(Path(ontology_path))
return pattern, engine.query(pattern)
def batch_query(patterns: list, ontology_path: str, max_workers: int = 4):
"""Execute multiple queries in parallel."""
results = {}
args_list = [(p, ontology_path) for p in patterns]
with ProcessPoolExecutor(max_workers=max_workers) as executor:
futures = {executor.submit(query_worker, args): args[0] for args in args_list}
for future in as_completed(futures):
pattern, result = future.result()
results[pattern] = result
return results
Monitoring & Logging¶
Structured Logging¶
import logging
import json
from datetime import datetime
class JSONFormatter(logging.Formatter):
def format(self, record):
log_record = {
'timestamp': datetime.utcnow().isoformat(),
'level': record.levelname,
'message': record.getMessage(),
'module': record.module,
'function': record.funcName,
}
if record.exc_info:
log_record['exception'] = self.formatException(record.exc_info)
return json.dumps(log_record)
# Configure logging
handler = logging.StreamHandler()
handler.setFormatter(JSONFormatter())
logger = logging.getLogger('mistaber')
logger.addHandler(handler)
logger.setLevel(logging.INFO)
Query Metrics¶
import time
from dataclasses import dataclass, field
from typing import Dict, List
@dataclass
class QueryMetrics:
total_queries: int = 0
total_time_ms: float = 0.0
queries_by_pattern: Dict[str, int] = field(default_factory=dict)
slow_queries: List[dict] = field(default_factory=list)
def record_query(self, pattern: str, duration_ms: float):
self.total_queries += 1
self.total_time_ms += duration_ms
self.queries_by_pattern[pattern] = self.queries_by_pattern.get(pattern, 0) + 1
if duration_ms > 1000: # Slow query threshold: 1 second
self.slow_queries.append({
'pattern': pattern,
'duration_ms': duration_ms,
'timestamp': time.time()
})
def get_stats(self) -> dict:
return {
'total_queries': self.total_queries,
'avg_query_time_ms': self.total_time_ms / max(self.total_queries, 1),
'top_patterns': sorted(
self.queries_by_pattern.items(),
key=lambda x: x[1],
reverse=True
)[:10],
'slow_query_count': len(self.slow_queries)
}
# Usage
metrics = QueryMetrics()
def timed_query(engine, pattern):
start = time.perf_counter()
result = engine.query(pattern)
duration_ms = (time.perf_counter() - start) * 1000
metrics.record_query(pattern, duration_ms)
return result
Health Endpoints¶
from flask import Flask, jsonify
from pathlib import Path
from mistaber.engine import HsrsEngine
app = Flask(__name__)
engine = HsrsEngine(Path("mistaber/ontology"))
@app.route('/health')
def health():
"""Basic health check."""
return jsonify({'status': 'healthy'})
@app.route('/health/ready')
def readiness():
"""Readiness check - verify engine is loaded."""
if engine.is_loaded:
return jsonify({'status': 'ready', 'ontology_loaded': True})
return jsonify({'status': 'not_ready', 'ontology_loaded': False}), 503
@app.route('/health/live')
def liveness():
"""Liveness check - verify basic query works."""
try:
worlds = engine.query("world(X)")
return jsonify({
'status': 'live',
'world_count': len(worlds)
})
except Exception as e:
return jsonify({'status': 'unhealthy', 'error': str(e)}), 503
Backup & Recovery¶
Ontology Versioning¶
Use Git for ontology version control:
# Initialize ontology as separate repo (if not already)
cd mistaber/ontology
git init
git add .
git commit -m "Initial ontology commit"
# Tag releases
git tag -a v1.0.0 -m "Basar Bechalav initial release"
git push origin v1.0.0
# Create branches for different traditions
git checkout -b sefardi-extensions
# Make changes
git commit -m "Add Yalkut Yosef specific rulings"
State Backup¶
For deployments with session state:
#!/bin/bash
# backup-state.sh
BACKUP_DIR="/backups/mistaber/$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP_DIR"
# Backup ontology
cp -r mistaber/ontology "$BACKUP_DIR/"
# Backup session state (if using encoding plugin)
cp .mistaber-session.yaml "$BACKUP_DIR/" 2>/dev/null || true
cp -r .mistaber-artifacts "$BACKUP_DIR/" 2>/dev/null || true
# Backup configuration
cp mistaber.yaml "$BACKUP_DIR/" 2>/dev/null || true
# Compress
tar -czf "$BACKUP_DIR.tar.gz" -C "$(dirname $BACKUP_DIR)" "$(basename $BACKUP_DIR)"
rm -rf "$BACKUP_DIR"
echo "Backup created: $BACKUP_DIR.tar.gz"
Disaster Recovery¶
#!/bin/bash
# restore-state.sh
BACKUP_FILE="$1"
if [ -z "$BACKUP_FILE" ]; then
echo "Usage: restore-state.sh <backup-file.tar.gz>"
exit 1
fi
# Create temp directory
TEMP_DIR=$(mktemp -d)
tar -xzf "$BACKUP_FILE" -C "$TEMP_DIR"
# Find the backup directory
BACKUP_DIR=$(find "$TEMP_DIR" -maxdepth 1 -type d | tail -1)
# Restore ontology
cp -r "$BACKUP_DIR/ontology"/* mistaber/ontology/
# Restore session state
cp "$BACKUP_DIR/.mistaber-session.yaml" . 2>/dev/null || true
cp -r "$BACKUP_DIR/.mistaber-artifacts" . 2>/dev/null || true
# Cleanup
rm -rf "$TEMP_DIR"
echo "Restore complete from $BACKUP_FILE"
Security Considerations¶
Access Control¶
For API deployments, implement authentication:
from functools import wraps
from flask import request, jsonify
import jwt
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers.get('Authorization', '').replace('Bearer ', '')
if not token:
return jsonify({'error': 'Missing authorization token'}), 401
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
request.user = payload
except jwt.InvalidTokenError:
return jsonify({'error': 'Invalid token'}), 401
return f(*args, **kwargs)
return decorated
@app.route('/query', methods=['POST'])
@require_auth
def query_endpoint():
# ... query logic
pass
Input Validation¶
Always validate query patterns:
import re
SAFE_PATTERN = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*\([^()]*\)$')
def validate_pattern(pattern: str) -> bool:
"""Validate that pattern is a safe ASP atom pattern."""
# Basic syntax check
if not SAFE_PATTERN.match(pattern):
return False
# Check for injection attempts
dangerous = ['#include', '#script', '#external', 'system(', 'exec(']
for d in dangerous:
if d.lower() in pattern.lower():
return False
return True
def safe_query(engine, pattern: str):
if not validate_pattern(pattern):
raise ValueError(f"Invalid or unsafe pattern: {pattern}")
return engine.query(pattern)
Data Privacy¶
For deployments handling sensitive queries:
import hashlib
import logging
def anonymize_pattern(pattern: str) -> str:
"""Hash variable values in pattern for logging."""
# Simple anonymization - hash any quoted strings
import re
def hash_match(match):
return f'"{hashlib.sha256(match.group(1).encode()).hexdigest()[:8]}"'
return re.sub(r'"([^"]*)"', hash_match, pattern)
# Log anonymized queries
logger.info(f"Query executed: {anonymize_pattern(pattern)}")
Quick Reference: Deployment Checklist¶
Before deploying to production:
- [ ] Python 3.10+ installed and verified
- [ ] Clingo 5.6.0+ installed and importable
- [ ] Ontology files present and validated
- [ ] Memory limits configured (minimum 2GB recommended)
- [ ] Health check endpoints implemented
- [ ] Logging configured with appropriate levels
- [ ] Backup strategy in place
- [ ] Input validation implemented
- [ ] Authentication configured (if public API)
- [ ] CI/CD pipeline tested and passing
- [ ] Rollback procedure documented
- [ ] Monitoring and alerting configured