Skip to main content

An action firewall for AI agents

Project description

Enact

You just gave an LLM access to real APIs. What happens when it does something stupid?

It already has. Replit's agent deleted a production database. Amazon Kiro caused a 13-hour AWS outage. Claude Code ran rm -rf on a home directory. These weren't bugs — the agents did exactly what they were told. The problem: nothing was checking whether they should.

Enact is the missing layer between your agent and the real world:

  1. Block dangerous actions before they fire — Python policies run before anything executes. Agent tries to push to main? Blocked. Tries to delete without a WHERE clause? Blocked.
  2. Execute deterministically — LLMs hallucinate. They call functions that don't exist, use wrong argument names, get column names wrong. Plain Python workflows do exactly what you wrote — they can be unit tested, reviewed in a PR, and git diff'd. LLM-generated actions cannot.
  3. Prove what happened — Every run (PASS or BLOCK) writes a cryptographically-signed JSON receipt: who ran what, which policies passed, what changed.
  4. Roll back in one call — When your agent wipes a database table, deletes the wrong branch, or trashes two hours of work, enact.rollback(run_id) brings it all back. Deleted rows restored. Branches recreated. PRs closed.
pip install enact-sdk

Quickstart (30 seconds)

git clone https://github.com/russellmiller3/enact
cd enact
pip install enact-sdk
python examples/quickstart.py

That's it. Three runs — one BLOCK, one PASS, one ROLLBACK — with signed receipts.

Want the full show? python examples/demo.py runs a 3-act scenario: an agent blocked from pushing to main, a normal PR workflow, and a database wipe rolled back in one command. No credentials needed.


Already have an agent? Migration takes 10 minutes.

Your agent's reasoning and planning logic doesn't change. You're adding a safety layer between it and your systems. Same calls, same results — now with policy enforcement, a signed audit trail, and rollback.

Three steps:

  1. Register your systems — swap your existing SDK clients for Enact connectors (same credentials, now policy-gated)
  2. Move your guard logic — any if/else checks you write become Python policy functions, or use our 24 built-in ones
  3. Replace direct callstool.do_thing() becomes enact.run()

Before (your agent today):

import github_sdk, psycopg2

# direct call — no policy check, no audit trail
github_sdk.create_pr(repo="myorg/app", branch="agent/fix-123", title="Fix bug")

# no WHERE protection — deletes every row
db.execute("DELETE FROM sessions")

After (wrapped with Enact):

from enact import EnactClient
from enact.connectors.github import GitHubConnector
from enact.connectors.postgres import PostgresConnector
from enact.policies.git import dont_push_to_main
from enact.policies.db import dont_delete_without_where

# one-time setup — replaces your SDK clients
enact = EnactClient(
    secret="...",
    systems={
        "github":   GitHubConnector(token="..."),
        "postgres": PostgresConnector(dsn="postgresql://..."),
    },
    policies=[dont_push_to_main, dont_delete_without_where],
)

# same intent — now policy-gated, receipt-backed, rollback-able
result, receipt = enact.run(
    workflow="agent_pr_workflow",
    user_email="agent@company.com",
    payload={"repo": "myorg/app", "branch": "agent/fix-123"},
)

Works with LangChain, CrewAI, OpenAI, Claude tool_use — any framework that can call a Python function. Your agent's prompting and reasoning stay exactly as-is.


Core Concepts

Think of Enact like a foreman supervising an AI carpenter. The carpenter is capable and fast, but needs oversight. When the carpenter says "I want to tear down this wall":

  1. Permit check — Before any tool is picked up, the foreman checks the plans. Load-bearing? Utilities inside? Approved? If not: work stops, written reason recorded.
  2. Blueprint — If approved, the carpenter follows exact step-by-step instructions — not just "tear down the wall" but each specific action in order. No improvising.
  3. Work log — A signed record of every nail pulled, every stud removed, exact before-and-after state. Cryptographically sealed so it can't be altered later.
  4. Change order — If the carpenter tore down the WRONG wall, the foreman issues a change order. Enact uses the work log to reverse every step and put it back.

The Four Pieces

Piece What it is Analogy
Policy A Python function that returns pass/fail The permit check
Workflow A Python function that does the actual work The blueprint the carpenter follows
Receipt A signed JSON record of what happened The signed work log
Rollback One call that reverses an entire run The change order + teardown

How They Fit Together

Agent wants to do something
         |
         v
    +----------+
    | POLICIES |  <-- "Is this approved?" (permit check)
    +----------+
         |
    PASS |  BLOCK --> Receipt (denied + reason)
         v
    +-----------+
    | WORKFLOW  |  <-- "Follow the blueprint, step by step"
    +-----------+
         |
         v
    +----------+
    | RECEIPT  |  <-- "Signed work log — what happened, what changed"
    +----------+
         |
    if needed:
         v
    +----------+
    | ROLLBACK |  <-- "Change order — reverse every step using the work log"
    +----------+

Why This Matters

These weren't bugs — the agents did exactly what they were told. The problem was no permit check, no work log, no way to undo it:

Incident What Happened Source
Replit Agent deleted a production database containing data for 2,400+ executives Fortune, Jul 2025
Amazon Kiro Agent deleted an EC2 environment → 13-hour AWS outage Awesome Agents, Feb 2026
Claude Code Agent ran rm -rf ~/ — wiped developer's entire home directory ByteIota, Dec 2025

How It Works

Prerequisite: The Connector

WHY: Your agent shouldn't call GitHub directly. You want a middleman that (a) limits what the agent can do and (b) records what actually happened. That's the Connector.

A Connector is a pre-built class that wraps an external system. You create one, hand it to Enact, and Enact passes it to your workflow. You never call GitHub (or Postgres, or the filesystem) directly anymore — you call the connector.

Think of it like handing a contractor a limited toolbox before you leave for work. The toolbox only contains the tools you specifically put in it. If the contractor hallucinates and decides to demolish a load-bearing wall — too bad, there's no sledgehammer in the box.

Here is how you create and use a connector:

from enact.connectors.github import GitHubConnector

# Create the connector — you only allow the two actions you actually need
gh = GitHubConnector(
    token="ghp_...",                              # Your GitHub Personal Access Token
    allowed_actions=["create_branch", "create_pr"] # ONLY these methods can be called
)

# Now call an action on it
result = gh.create_branch(repo="owner/repo", branch="agent/fix-149")

# Every action returns an ActionResult — a mini-receipt for that one action
print(result.success)  # True or False
print(result.output)   # {"branch": "agent/fix-149"}

Why allowed_actions matters: Policies are your smart rules — they enforce your business logic and the scenarios you anticipated. allowed_actions is your hardcoded floor: even if your agent tries something you never thought to write a policy for, it simply can't execute an action that isn't on the list. Policies handle what you thought of. allowed_actions handles everything you didn't.

# This is what happens if the agent goes rogue:
gh.delete_branch(repo="owner/repo", branch="main")
# -> PermissionError: Action 'delete_branch' not in allowlist

Enact ships connectors for GitHub, Postgres, the filesystem, and Slack. You don't write these — you import and configure them.

Prerequisite: The Context

The WorkflowContext is the "bag of data" that travels through the entire system — passed to every policy check and every action.

Think of it like a delivery package. The context contains:

  1. Who sent it (user_email)
  2. What they want done (payload)
  3. The tools they can use (systems)

Here is what a WorkflowContext looks like in memory:

# Enact builds this automatically — you never create it manually.
# It's shown here so you understand what your workflow receives.
context = WorkflowContext(
    user_email="agent@company.com",             # Who is making the request
    payload={                                   # The data the agent wants to act on
        "repo": "owner/repo",
        "branch": "agent/fix-149",
    },
    systems={                                   # The connectors, keyed by name
        "github": GitHubConnector(              # The actual connector you configured
            token="ghp_...",
            allowed_actions=["create_branch", "create_pr"],
        ),
    },
)

Step 1: Define what your agent should do

WHY: Instead of your agent running arbitrary code against GitHub, you give it a script to follow — a plain Python function. Enact runs that function. This way, every action is recorded, every failure is caught, and you can roll back the whole thing.

A workflow is a Python function that takes a context (the bag from above) and returns a list of ActionResult objects — one per action taken. If you're coming from Semantic Kernel, LangChain tools, or MCP — a workflow is your skill, hardened: same callable interface, plus policy enforcement, a signed receipt, and rollback.

from enact.models import WorkflowContext, ActionResult

def agent_pr_workflow(context: WorkflowContext) -> list[ActionResult]:
    # Pull the connector and payload data out of the context bag
    gh = context.systems["github"]      # The GitHubConnector you configured
    repo = context.payload["repo"]      # "owner/repo"
    branch = context.payload["branch"]  # "agent/fix-149"

    results = []

    # Take the first action — create the branch
    result1 = gh.create_branch(repo=repo, branch=branch)
    results.append(result1)  # Keep a running log of everything that happened

    # Stop early if it failed — no point creating a PR for a branch that doesn't exist
    if not result1.success:
        return results

    # Take the second action — open the pull request
    # f"Agent: {branch}" is Python string interpolation: becomes "Agent: agent/fix-149"
    result2 = gh.create_pr(repo=repo, title=f"Agent: {branch}", body="Automated PR", head=branch)
    results.append(result2)

    return results  # Enact signs this list into a receipt

Step 2: Define the policies it should follow

WHY: The workflow does whatever you tell it to. Policies decide whether it should run at all. They run first, before any action fires. If any policy fails, the whole run is blocked and you get a receipt explaining why.

A policy is a plain Python function — no LLMs, no magic. It reads the context and returns pass or fail with a reason.

Here's a concrete example. The standard engineering rule is: no one pushes directly to main. Instead, changes go into a separate branch, get reviewed by a human in a Pull Request (PR), and only then get merged. This gives you a checkpoint before anything goes live.

Agents break this rule constantly. They push directly to main because no one told them not to — and because they can. The Amazon Kiro incident was exactly this pattern: an agent made a direct infrastructure change with no review step, and caused a 13-hour AWS outage. This policy is the guardrail: if the agent tries to target main, the run is blocked before any code is touched.

from enact.models import WorkflowContext, PolicyResult

def dont_push_to_main(context: WorkflowContext) -> PolicyResult:
    branch = context.payload.get("branch", "")
    branch_is_not_main = branch.lower() not in ("main", "master")
    return PolicyResult(
        policy="dont_push_to_main",
        passed=branch_is_not_main,
        reason="Branch is not main/master" if branch_is_not_main else f"Direct push to '{branch}' is blocked",
    )

How the check works — three logical steps:

┌─────────────────────────────────────────────────────────┐
│  STEP 1: Read the branch name from the agent's request  │
│    context.payload.get("branch", "")  -->  "main"       │
└────────────────────────┬────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────┐
│  STEP 2: Is this branch safe to push to?                │
│    branch_is_not_main = branch not in ("main","master") │
│                                                         │
│    "agent/fix-149" -->  branch_is_not_main = True   ✅  │
│    "main"          -->  branch_is_not_main = False  🚫  │
└────────────────────────┬────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────┐
│  STEP 3: passed = branch_is_not_main                    │
│                                                         │
│    True   -->  PASS ✅                                  │
│    False  -->  BLOCK 🚫                                 │
└─────────────────────────────────────────────────────────┘

You don't need to write most policies yourself — Enact ships 24 built-in ones. See Built-in Policies below.

Step 3: Wire it all up and run

WHY: Now you hand everything to EnactClient — your connectors, policies, and workflows. Then you call enact.run() the same way your agent would. Enact handles the policy check, the execution, and the receipt.

from enact import EnactClient
from enact.connectors.github import GitHubConnector
from enact.workflows.agent_pr_workflow import agent_pr_workflow
from enact.policies.git import dont_push_to_main, require_branch_prefix

enact = EnactClient(
    systems={
        "github": GitHubConnector(
            token="ghp_...",                                 # Your GitHub PAT
            allowed_actions=["create_branch", "create_pr"], # Only these are allowed
        )
    },
    policies=[
        dont_push_to_main,           # A plain policy function (defined above)
        require_branch_prefix("agent/"),  # A policy *factory* — calling it with "agent/"
                                          # returns a configured policy function
    ],
    workflows=[agent_pr_workflow],   # Register the workflow by passing the function
    secret="your-secret-here",       # Min 32 chars. Or: export ENACT_SECRET="..." in shell
)

# This is what your agent calls. It returns two things:
result, receipt = enact.run(
    workflow="agent_pr_workflow",        # Which workflow to run (must be registered above)
    user_email="agent@company.com",      # Who is making the request (for audit trail)
    payload={"repo": "owner/repo", "branch": "agent/fix-149"},  # Data for the workflow
)

print(result.decision)   # "PASS" or "BLOCK"
print(receipt.run_id)    # UUID — use this to look up or roll back the run

Step 4: Read the receipts

Every run — PASS or BLOCK — writes a signed JSON receipt to receipts/:

{
  "run_id": "a1b2c3d4-...",
  "workflow": "agent_pr_workflow",
  "user_email": "agent@company.com",
  "decision": "PASS",
  "policy_results": [
    {
      "policy": "dont_push_to_main",
      "passed": true,
      "reason": "Branch is not main/master"
    },
    {
      "policy": "require_branch_prefix",
      "passed": true,
      "reason": "Branch 'agent/fix-149' has required prefix"
    }
  ],
  "actions_taken": [
    { "action": "create_branch", "system": "github", "success": true },
    { "action": "create_pr", "system": "github", "success": true }
  ],
  "timestamp": "2026-02-26T03:30:00Z",
  "signature": "hmac-sha256-hex..."
}

Verify a receipt hasn't been tampered with:

from enact.receipt import verify_signature
is_valid = verify_signature(receipt, secret="your-secret")

Receipt Browser (local UI)

Browse, filter, and verify your receipts locally — no cloud required.

enact-ui                           # serves receipts/ on http://localhost:8765
enact-ui --port 9000               # custom port
enact-ui --dir /path/to/receipts   # custom directory
enact-ui --secret YOUR_SECRET      # enables signature verification in the UI

The browser shows every run (PASS / BLOCK / ROLLED_BACK), lets you click into the full JSON, and highlights invalid signatures. Dark mode toggle included. Zero extra dependencies — ships with enact-sdk.

Step 5: Rollback (if something goes wrong)

WHY: Say the agent_pr_workflow from Step 1 ran — it created agent/fix-149, opened a PR, and merged it straight to main by mistake. You need to undo all three steps. One call.

rollback() does four things in order:

  1. Loads the receipt by run_id — looks up receipts/a1b2c3d4-....json
  2. Verifies the signature — if the receipt was tampered with, rollback refuses to run
  3. Walks actions_taken in reverse — last action first, so nothing is orphaned
  4. Calls the undo action for each step and writes a new rollback receipt

Here's what "in reverse" looks like for a workflow that created a branch, opened a PR, then merged it:

Original run (forward):          Rollback (reverse):

  Step 1: create_branch           Step 3 undone: revert_commit  (new commit on main)
  Step 2: create_pr            →  Step 2 undone: close_pr
  Step 3: merge_pr                Step 1 undone: delete_branch

Why reverse? merge_pr happened last — you have to undo it first before closing the PR makes sense. Reverse order preserves the dependency chain.

revert_commit is git revert -m 1 <sha> under the hood — it adds a new commit to main that restores its pre-merge state. Safe on protected branches; no force-push needed. The merge SHA is captured automatically in the receipt when merge_pr runs.

# receipt.run_id came from the enact.run() call in Step 3
rollback_result, rollback_receipt = enact.rollback(receipt.run_id)

print(rollback_result.decision)          # "ROLLED_BACK"
print(rollback_result.actions_reversed)  # ["revert_commit", "close_pr", "delete_branch"]

The rollback receipt looks like this — note the revert_sha showing exactly what was created on main:

{
  "run_id": "rb-9f8e7d6c-...",
  "original_run_id": "a1b2c3d4-...",
  "workflow": "agent_pr_workflow",
  "decision": "ROLLED_BACK",
  "actions_reversed": [
    {
      "action": "revert_commit",
      "system": "github",
      "success": true,
      "output": { "revert_sha": "f7c3a1b...", "reverted_merge": "e9d2c4a...", "base_branch": "main" }
    },
    { "action": "close_pr",      "system": "github", "success": true },
    { "action": "delete_branch", "system": "github", "success": true }
  ],
  "timestamp": "2026-02-26T03:35:00Z",
  "signature": "hmac-sha256-hex..."
}

One caveat on re-merging: A revert doesn't erase history. If you fix the issue and try to re-merge the same branch later, Git will skip those commits (it thinks they're already in main). You'd need to git revert <revert_sha> first — "undo the undo" — then merge. This is standard Git behavior, not an Enact quirk.

What if an action truly can't be undone? push_commit has no safe inverse without a force-push, which GitHub blocks on protected branches. If rollback hits one of these, it stops, records which action couldn't be reversed, and tells you exactly what to fix manually. It won't silently skip it.


Connectors & Allowed Actions

You might be thinking: "Don't we already have Policies?" Yes — but allowed_actions adds a complementary layer that works differently.

  • Policies are your business rules: "You can push code, but not to the master branch."
  • allowed_actions is your hardcoded floor: "This connector can only ever call these two methods. Full stop."

Policies handle the scenarios you anticipated. allowed_actions caps the blast radius for everything else — even actions you never thought to write a policy for. The list is checked before any API call, every time, with no exceptions.

Available Actions by Connector

System Actions Rollback Idempotent
GitHub create_branch, create_pr, create_issue, delete_branch, merge_pr Yes — merge_pr via revert_commit; except push_commit Yes — already_done convention
Postgres select_rows, insert_row, update_row, delete_row Yes — pre-SELECT captures state Yes
Filesystem read_file, write_file, delete_file, list_dir Yes — content captured before mutation Yes
Slack post_message, delete_message Yes — post_message via delete_message (bot token must have chat:delete scope) No — posting the same text twice is two messages, not a duplicate

What Rollback Can and Can't Undo

Action Rollback? How
github.create_branch Deletes the branch
github.create_pr Closes the PR
github.merge_pr git revert -m 1 <sha> — adds a new commit to the base branch restoring pre-merge state. Safe on protected branches; no force-push.
github.delete_branch Recreates branch at the captured SHA
github.push_commit Un-pushing requires a destructive force-push, which GitHub blocks on protected branches
postgres.insert_row Deletes the inserted row
postgres.update_row Restores pre-update values (pre-SELECT captures state)
postgres.delete_row Re-inserts every deleted row (pre-SELECT captures state)
postgres.DROP TABLE Not a connector action — blocked by block_ddl policy. Even with captured rows, you'd lose indexes, constraints, sequences, and foreign keys. Prevention beats fake recovery.
postgres.TRUNCATE Same as above — blocked by block_ddl
filesystem.write_file Restores previous content (or deletes if file was new)
filesystem.delete_file Recreates file with captured content
slack.post_message Deletes the posted message via chat.delete using the ts timestamp captured at post time
slack.delete_message You can't un-delete a Slack message

One caveat on merge_pr rollback: After reverting a merge, if you fix the issue and try to re-merge the same branch, Git will skip those commits (they look already-merged). Revert the revert commit first (git revert <revert_sha>), then re-merge. This is standard Git behavior.


Built-in Policies

Enact ships 30 built-in policies across 9 categories so you don't have to write them from scratch:

Category Policies What they block
Git dont_push_to_main, require_branch_prefix, max_files_per_commit, dont_delete_branch, dont_merge_to_main Direct pushes to main, wrong branch names, blast radius
Database dont_delete_row, dont_delete_without_where, dont_update_without_where, protect_tables, block_ddl Dangerous deletes, unscoped updates, DDL like DROP TABLE
Filesystem dont_delete_file, restrict_paths, block_extensions File deletions, path traversal, sensitive files (.env, .key)
Access contractor_cannot_write_pii, require_actor_role, require_user_role, dont_read_sensitive_tables, dont_read_sensitive_paths, require_clearance_for_path Unauthorized access, PII exposure
CRM dont_duplicate_contacts, limit_tasks_per_contact Duplicate records, rate limiting
Time within_maintenance_window, code_freeze_active Actions outside allowed hours, during code freezes
Slack require_channel_allowlist, block_dms Off-list channel posts, direct messages to users
Email no_mass_emails, no_repeat_emails Mass email blasts, spamming the same recipient
Cloud dont_delete_without_human_ok S3/GDrive deletions without cryptographic HITL approval
from enact.policies.git import dont_push_to_main, require_branch_prefix
from enact.policies.db import protect_tables, block_ddl
from enact.policies.time import code_freeze_active
from enact.policies.slack import require_channel_allowlist, block_dms
from enact.policies.email import no_mass_emails
from enact.policies.cloud_storage import dont_delete_without_human_ok

Security

Receipts are HMAC-SHA256 signed. The signature covers every field — tampering with any field invalidates it.

export ENACT_SECRET="$(openssl rand -hex 32)"

Or pass secret= to EnactClient. Minimum 32 characters. No default.

For dev/testing only: EnactClient(..., secret="short", allow_insecure_secret=True)

Rollback verifies the receipt signature before executing any reversal — tampered receipts can't trigger unintended operations.


Cloud Features

Push receipts to the Enact cloud and use human-in-the-loop gates from any workflow.

from enact import EnactClient

enact = EnactClient(
    systems={"github": gh},
    policies=[dont_push_to_main],
    workflows=[agent_pr_workflow],
    secret="your-secret",
    cloud_api_key="eak_...",   # get at enact.cloud — enables cloud features
)

Push receipts to cloud storage:

result, receipt = enact.run(...)
enact.push_receipt_to_cloud(receipt)   # receipt now searchable in cloud UI

Human-in-the-loop gate — pause a workflow and email a human to approve before continuing:

result, receipt = enact.run_with_hitl(
    workflow="agent_pr_workflow",
    user_email="agent@company.com",
    payload={"repo": "myorg/app", "branch": "agent/nuke-main"},
    notify_email="ops@company.com",    # who gets the approve/deny email
    timeout_seconds=3600,              # auto-deny after 1 hour of silence
)

print(result.decision)   # "PASS" if approved, "BLOCK" if denied or timed out

The approval email contains a signed link. Clicking approve or deny fires a callback and writes a HITL receipt. No credentials or login needed for the approver.

Status badge — embed in your README to show real-time pass/block rate for a workflow:

![agent_pr_workflow](https://enact.cloud/badge/your-team-id/agent_pr_workflow.svg)

Run Tests

pytest tests/ -v
# 356+ tests, 0 failures (SDK + cloud)

Environment Variables

Variable Required Purpose
ENACT_SECRET Yes (or pass secret=) HMAC signing key. 32+ characters.
GITHUB_TOKEN For GitHubConnector GitHub PAT or App token
SLACK_BOT_TOKEN For SlackConnector Slack bot token (xoxb-...). Needs chat:write scope; add chat:delete to enable rollback.
ENACT_FREEZE Optional Set to 1 to activate code_freeze_active policy
CLOUD_API_KEY For cloud features API key from enact.cloud — enables receipt push + HITL
CLOUD_SECRET Cloud backend only Server-side signing secret for the cloud backend
ENACT_EMAIL_DRY_RUN Cloud backend only Set to 1 to skip real email sends in dev/test

Deployment

The Enact landing page is hosted on Vercel with DNS managed via Porkbun.

  • URL: https://enact.cloud
  • Frontend: Static HTML (index.html)
  • CI/CD: Auto-deploy on push to master branch

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

enact_sdk-0.5.tar.gz (105.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

enact_sdk-0.5-py3-none-any.whl (71.6 kB view details)

Uploaded Python 3

File details

Details for the file enact_sdk-0.5.tar.gz.

File metadata

  • Download URL: enact_sdk-0.5.tar.gz
  • Upload date:
  • Size: 105.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.6

File hashes

Hashes for enact_sdk-0.5.tar.gz
Algorithm Hash digest
SHA256 022c5c77c8a642df3d33532bbce1a77ecd866489d32b2ad9a75840e24d35ce5e
MD5 3495d90ed3af85a71b78eff61be77c16
BLAKE2b-256 63dbbd3eecd388feb757b04e41ce2d4eae155a190e15bd05f4f2896542426fe7

See more details on using hashes here.

File details

Details for the file enact_sdk-0.5-py3-none-any.whl.

File metadata

  • Download URL: enact_sdk-0.5-py3-none-any.whl
  • Upload date:
  • Size: 71.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.6

File hashes

Hashes for enact_sdk-0.5-py3-none-any.whl
Algorithm Hash digest
SHA256 00ba6bef19aa9970776cadd6a90b84bf538e3d927cac4be611e7499d7d5196f2
MD5 33bfccfc7395a2ca8ef2a51f836e3be0
BLAKE2b-256 ef3b11131c1405d4864640844601b248159eeb1c0193ecbac019ceb0c491e41f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page