Skip to main content
This guide covers three migration scenarios: adopting Attesta for the first time (replacing manual approval flows), upgrading between Attesta versions, and updating domain profiles.

Migrating from Manual Approval Flows

If your codebase already has ad-hoc approval logic — input() prompts, Slack confirmation bots, or custom approval middleware — Attesta can replace them with a unified, auditable framework.

Before: Manual Approval Patterns

# Before: scattered input() calls
def delete_user(user_id: str) -> str:
    confirm = input(f"Delete user {user_id}? [y/N] ")
    if confirm.lower() != "y":
        raise RuntimeError("Aborted")
    # ... delete logic
    return f"Deleted {user_id}"

After: Attesta

1

Install Attesta

pip install attesta
2

Replace Inline Prompts

Replace manual input() calls with the @gate decorator:
def delete_user(user_id: str) -> str:
    confirm = input(f"Delete user {user_id}? [y/N] ")
    if confirm.lower() != "y":
        raise RuntimeError("Aborted")
    return db.delete(user_id)
Attesta now handles the approval prompt, risk scoring, minimum review time enforcement, and audit logging — all automatically.
3

Replace Custom Decorators

If you had a homegrown approval decorator, replace it with @gate:
@require_approval
def deploy(service: str, version: str) -> str:
    return f"Deployed {service} v{version}"
4

Replace Slack Bots

If you had a custom Slack approval bot, replace it with an Attesta SlackRenderer:
async def deploy_with_approval(service, version):
    request_id = await slack.post_approval_request(...)
    response = await slack.wait_for_response(request_id)
    if response != "approved":
        raise RuntimeError("Denied")
    return do_deploy(service, version)
5

Handle AttestaDenied

Replace your custom error handling with AttestaDenied:
try:
    deploy("api", "2.0")
except RuntimeError as e:
    if "Denied" in str(e) or "Aborted" in str(e):
        log.info("Deployment cancelled by operator")
    else:
        raise
6

Add Configuration

Create an attesta.yaml to centralize your policy:
attesta.yaml
policy:
  fail_mode: deny
  minimum_review_seconds:
    medium: 3
    high: 10
    critical: 30

risk:
  overrides:
    delete_user: critical
    deploy: high
    transfer_funds: critical

trust:
  initial_score: 0.3
  ceiling: 0.85
  influence: 0.25

audit:
  path: .attesta/audit.jsonl
Then load it:
from attesta import Attesta

attesta = Attesta.from_config("attesta.yaml")

@attesta.gate
def delete_user(user_id: str) -> str:
    ...

Migration Checklist

Manual PatternAttesta Replacement
input("Approve?")@gate decorator
Custom approval decorator@gate or @attesta.gate
Slack approval botSlackRenderer
Custom audit loggingAuditLogger or custom AuditLogger protocol
Risk classification logicRiskScorer (default or custom)
Per-action risk levelsrisk_hints={} or YAML overrides:
Environment checksenvironment="production" parameter
When migrating, do not remove your old approval logic until you have verified that Attesta is working correctly in your environment. Run both systems in parallel during the transition period, with the old system as a fallback.

Migrating Between Attesta Versions

v0.x to v1.0 (Current)

Attesta follows semantic versioning. The v0.x series is the initial release; v1.0 will be the first stable API.

Breaking Changes to Watch For

Imports were consolidated in v0.1.0. If you are upgrading from an earlier pre-release:
from gatekeeper import gate
from gatekeeper.core import RiskScorer
The package was renamed from gatekeeper-ai to attesta. Update all imports.

Upgrade Procedure

1

Update the Package

pip install --upgrade attesta
2

Check for Deprecation Warnings

Run your test suite with warnings enabled:
python -W all -m pytest tests/
Attesta emits DeprecationWarning for legacy features that will be removed in a future version.
3

Update Configuration Format

If you are still using the flat YAML format, migrate to the rich format. Use from_config() — it auto-detects both formats, so you can migrate incrementally.
from attesta import Attesta

# Both formats work -- no code changes needed
attesta = Attesta.from_config("attesta.yaml")
4

Verify Audit Chain Continuity

After upgrading, verify that the existing audit chain is still intact:
from attesta.core.audit import AuditLogger

logger = AuditLogger(path=".attesta/audit.jsonl")
intact, total, broken = logger.verify_chain()

assert intact, f"Broken links at indices: {broken}"
print(f"Audit chain verified: {total} entries intact")
5

Update Trust Engine Storage

If you are using persistent trust storage, the TrustEngine will load existing data automatically. No manual migration is needed for trust profiles.
from attesta.core.trust import TrustEngine
from pathlib import Path

engine = TrustEngine(storage_path=Path(".attesta/trust.json"))
# Existing profiles are loaded automatically
6

Run the Full Test Suite

pytest tests/ -v

Timeout Policy Migration Notes (fail_mode)

Recent releases wire policy.fail_mode and policy.timeout_seconds directly into runtime gate behavior (Python and TypeScript SDKs). For challenge timeouts:
fail_modeRuntime verdictExecutes protected action?
denyTIMED_OUTNo
allowAPPROVEDYes
escalateESCALATEDNo
Example:
policy:
  fail_mode: escalate
  timeout_seconds: 300
If you previously assumed timeouts always denied with TIMED_OUT, audit your policy config before upgrading and explicitly set fail_mode: deny.

Upgrading Domain Profiles

Domain profiles encode industry-specific risk patterns, sensitive terms, and compliance references. When regulations change or your domain knowledge evolves, you need to update them.

Updating a Custom Profile

Custom profiles registered with register_preset() are loaded at runtime. Update your profile definitions and re-register to apply changes. To upgrade:
pip install --upgrade attesta
Custom profiles take effect immediately when loaded via configuration:
attesta.yaml
domain: my-domain

Overriding Profile Fields

If you need to customize a registered profile, load the preset and modify specific fields:
from attesta.domains.presets import load_preset
from attesta.domains.profile import RiskPattern, EscalationRule

# Load a registered profile
profile = load_preset("my-domain")

# Add a custom risk pattern
profile.risk_patterns.append(
    RiskPattern(
        pattern=r"sensitive|restricted",
        target="any",
        risk_contribution=0.9,
        name="sensitive_data",
        description="Access to sensitive/restricted data.",
    )
)

# Add a custom critical action
profile.critical_actions.append("export_genetic_data")

# Add custom vocabulary for teach-back challenges
profile.required_vocabulary.extend(["GINA", "genetic", "consent"])

Creating a Custom Profile

For complete control, create a profile from scratch:
from attesta.domains.profile import (
    DomainProfile, RiskPattern, EscalationRule,
    DomainChallengeTemplate, registry,
)

my_profile = DomainProfile(
    name="my-company",
    display_name="My Company Internal",
    description="Risk profile for internal tools and services.",
    risk_patterns=[
        RiskPattern(
            pattern=r"customer_data|user_pii",
            target="any",
            risk_contribution=0.8,
            name="customer_pii",
            description="Access to customer personal data.",
            compliance_refs=["GDPR Art. 6", "CCPA"],
        ),
        RiskPattern(
            pattern=r"billing|invoice|payment",
            target="function_name",
            risk_contribution=0.7,
            name="financial_ops",
            description="Financial operations.",
            compliance_refs=["SOX", "PCI-DSS"],
        ),
    ],
    sensitive_terms={
        "ssn": 0.95,
        "credit_card": 0.9,
        "password": 0.85,
        "api_key": 0.8,
    },
    critical_actions=[
        "delete_customer",
        "process_refund",
        "modify_billing",
        "export_all_users",
    ],
    safe_actions=[
        "get_status",
        "list_products",
        "search_docs",
    ],
    compliance_frameworks=["GDPR", "SOX", "PCI-DSS"],
    escalation_rules=[
        EscalationRule(
            condition="risk_score > 0.9",
            action="require_multi_party",
            required_approvers=2,
            notify_roles=["security-team", "compliance"],
            description="Very high risk requires dual approval.",
        ),
    ],
    challenge_templates=[
        DomainChallengeTemplate(
            question_template=(
                "What customer data will {action} access "
                "and what is the business justification?"
            ),
            answer_hints=["customer", "data", "justification", "ticket"],
            context_vars=["action"],
            challenge_type="teach_back",
            min_risk_level="high",
        ),
    ],
    base_risk_floor=0.15,
    production_multiplier=1.5,
)

# Register for use
registry.register(my_profile)

Merging Profiles

When your organization spans multiple regulatory domains, merge profiles:
from attesta.domains.presets import load_preset
from attesta.domains.profile import DomainRegistry

profile_a = load_preset("profile-a")
profile_b = load_preset("profile-b")

reg = DomainRegistry()
reg.register(profile_a)
reg.register(profile_b)

# Merge produces a composite with the most conservative settings
merged = reg.merge(profile_a, profile_b)
print(f"Merged profile: {merged.display_name}")
print(f"Compliance frameworks: {merged.compliance_frameworks}")
Or via YAML:
attesta.yaml
domain:
  - profile-a
  - profile-b
When merging profiles, conflicting scalar values (like base_risk_floor) resolve to the most conservative (highest) value. Sensitive term weights resolve to the maximum across profiles. This ensures that merged profiles are at least as strict as any individual profile.

Profile Version Tracking

Track profile versions in your configuration for reproducibility:
attesta.yaml
# Activate a registered domain profile
domain: my-domain
To check the current profile contents:
from attesta.domains.presets import load_preset

profile = load_preset("my-domain")
print(f"Profile: {profile.display_name}")
print(f"Risk patterns: {len(profile.risk_patterns)}")
print(f"Critical actions: {profile.critical_actions}")
print(f"Compliance: {profile.compliance_frameworks}")

Migration Support Matrix

FromToComplexityNotes
input() prompts@gateLowDirect replacement
Custom decorator@gate / @attesta.gateLowMap parameters to Attesta equivalents
Slack approval botSlackRendererMediumReimplement webhook handling
Custom risk logicRiskScorer protocolMediumWrap existing logic in score() method
Flat YAML configRich YAML configLowBoth formats supported
gatekeeper-ai packageattesta packageLowRename imports

Quick Start

Get started with Attesta from scratch

Configuration

Full YAML configuration reference