enact-sdk 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. enact_sdk-0.1.0/LICENSE +94 -0
  2. enact_sdk-0.1.0/PKG-INFO +261 -0
  3. enact_sdk-0.1.0/README.md +236 -0
  4. enact_sdk-0.1.0/enact/__init__.py +19 -0
  5. enact_sdk-0.1.0/enact/client.py +168 -0
  6. enact_sdk-0.1.0/enact/connectors/__init__.py +1 -0
  7. enact_sdk-0.1.0/enact/connectors/github.py +259 -0
  8. enact_sdk-0.1.0/enact/models.py +145 -0
  9. enact_sdk-0.1.0/enact/policies/__init__.py +1 -0
  10. enact_sdk-0.1.0/enact/policies/access.py +98 -0
  11. enact_sdk-0.1.0/enact/policies/crm.py +118 -0
  12. enact_sdk-0.1.0/enact/policies/git.py +137 -0
  13. enact_sdk-0.1.0/enact/policies/time.py +73 -0
  14. enact_sdk-0.1.0/enact/policy.py +85 -0
  15. enact_sdk-0.1.0/enact/receipt.py +163 -0
  16. enact_sdk-0.1.0/enact/workflows/__init__.py +1 -0
  17. enact_sdk-0.1.0/enact/workflows/agent_pr_workflow.py +71 -0
  18. enact_sdk-0.1.0/enact/workflows/db_safe_insert.py +81 -0
  19. enact_sdk-0.1.0/enact_sdk.egg-info/PKG-INFO +261 -0
  20. enact_sdk-0.1.0/enact_sdk.egg-info/SOURCES.txt +30 -0
  21. enact_sdk-0.1.0/enact_sdk.egg-info/dependency_links.txt +1 -0
  22. enact_sdk-0.1.0/enact_sdk.egg-info/requires.txt +21 -0
  23. enact_sdk-0.1.0/enact_sdk.egg-info/top_level.txt +1 -0
  24. enact_sdk-0.1.0/pyproject.toml +33 -0
  25. enact_sdk-0.1.0/setup.cfg +4 -0
  26. enact_sdk-0.1.0/tests/test_client.py +194 -0
  27. enact_sdk-0.1.0/tests/test_git_policies.py +196 -0
  28. enact_sdk-0.1.0/tests/test_github.py +159 -0
  29. enact_sdk-0.1.0/tests/test_policies.py +252 -0
  30. enact_sdk-0.1.0/tests/test_policy_engine.py +106 -0
  31. enact_sdk-0.1.0/tests/test_receipt.py +171 -0
  32. enact_sdk-0.1.0/tests/test_workflows.py +118 -0
@@ -0,0 +1,94 @@
1
+ Elastic License 2.0
2
+
3
+ URL: https://www.elastic.co/licensing/elastic-license
4
+
5
+ ## Acceptance
6
+
7
+ By using the software, you agree to all of the terms and conditions below.
8
+
9
+ ## Copyright License
10
+
11
+ The licensor grants you a non-exclusive, royalty-free, worldwide,
12
+ non-sublicensable, non-transferable license to use, copy, distribute, make
13
+ available, and prepare derivative works of the software, in each case subject
14
+ to the limitations and conditions below.
15
+
16
+ ## Limitations
17
+
18
+ You may not provide the software to third parties as a hosted or managed
19
+ service, where the service provides users with access to any substantial set
20
+ of the features or functionality of the software.
21
+
22
+ You may not sell, resell, sublicense, or otherwise commercially distribute the
23
+ software itself as a standalone product or as the primary component of a paid
24
+ offering. For purposes of this restriction, "Sell" means practicing any or all
25
+ of the rights granted to you under this license to provide to third parties,
26
+ for a fee or other consideration, a product or service whose value derives
27
+ entirely or substantially from the functionality of the software.
28
+
29
+ You may not move, change, disable, or circumvent the license key functionality
30
+ in the software, and you may not remove or obscure any functionality in the
31
+ software that is protected by the license key.
32
+
33
+ You may not alter, remove, or obscure any licensing, copyright, or other
34
+ notices of the licensor in the software. Any use of the licensor's trademarks
35
+ is subject to applicable law.
36
+
37
+ ## Patents
38
+
39
+ The licensor grants you a license, under any patent claims the licensor can
40
+ license, or becomes able to license, to make, have made, use, sell, offer for
41
+ sale, import and otherwise transfer the software. This license does not cover
42
+ any patent claims that you cause to be infringed by modifications or additions
43
+ to the software. If you or your company make any written claim that the
44
+ software infringes or contributes to infringement of any patent, your patent
45
+ license for the software granted under these terms ends immediately.
46
+
47
+ ## Notices
48
+
49
+ You must ensure that anyone who gets a copy of any part of the software from
50
+ you also gets a copy of these terms.
51
+
52
+ If you modify the software, you must include in any modified copies of the
53
+ software prominent notices stating that you have modified the software.
54
+
55
+ ## No Other Rights
56
+
57
+ These terms do not imply any licenses other than those expressly granted in
58
+ these terms.
59
+
60
+ ## Termination
61
+
62
+ If you use the software in violation of these terms, such use is not licensed,
63
+ and your licenses will automatically terminate. If the licensor provides you
64
+ with a notice of your violation, and you cease all violation of this license
65
+ no later than 30 days after you receive that notice, your licenses will be
66
+ reinstated retroactively. However, if you violate these terms after such
67
+ reinstatement, any additional violation of these terms will cause your licenses
68
+ to terminate automatically and permanently.
69
+
70
+ ## No Liability
71
+
72
+ As far as the law allows, the software comes as is, without any warranty or
73
+ condition, and the licensor will not be liable to you for any damages arising
74
+ out of these terms or the use or nature of the software, under any kind of
75
+ legal claim.
76
+
77
+ ## Definitions
78
+
79
+ The *licensor* is the entity offering these terms, and the *software* is the
80
+ software the licensor makes available under these terms, including any portion
81
+ of it.
82
+
83
+ *you* refers to the individual or entity agreeing to these terms.
84
+
85
+ *your company* is any legal entity, sole proprietorship, or other kind of
86
+ organization that you work for, plus all organizations that have control over,
87
+ are under the control of, or are under common control with that organization.
88
+ *control* means ownership of substantially all the assets of an entity, or the
89
+ power to direct its management and legal affairs.
90
+
91
+ *your licenses* are all the licenses granted to you for the software under
92
+ these terms.
93
+
94
+ *use* includes any use of the software, including use in modified form.
@@ -0,0 +1,261 @@
1
+ Metadata-Version: 2.4
2
+ Name: enact-sdk
3
+ Version: 0.1.0
4
+ Summary: An action firewall for AI agents
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: pydantic>=2.0
9
+ Requires-Dist: python-dotenv
10
+ Provides-Extra: postgres
11
+ Requires-Dist: psycopg2-binary; extra == "postgres"
12
+ Provides-Extra: github
13
+ Requires-Dist: PyGithub; extra == "github"
14
+ Provides-Extra: hubspot
15
+ Requires-Dist: hubspot-api-client; extra == "hubspot"
16
+ Provides-Extra: all
17
+ Requires-Dist: psycopg2-binary; extra == "all"
18
+ Requires-Dist: PyGithub; extra == "all"
19
+ Requires-Dist: hubspot-api-client; extra == "all"
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest; extra == "dev"
22
+ Requires-Dist: pytest-asyncio; extra == "dev"
23
+ Requires-Dist: responses; extra == "dev"
24
+ Dynamic: license-file
25
+
26
+ # Enact
27
+
28
+ **An action firewall for AI agents.**
29
+
30
+ Enact sits between your AI agent and the outside world. Every action goes through a policy gate first. If it passes, Enact executes it and returns a signed receipt. If it doesn't, nothing happens.
31
+
32
+ ```python
33
+ from enact import EnactClient
34
+ from enact.connectors.github import GitHubConnector
35
+ from enact.workflows.agent_pr_workflow import agent_pr_workflow
36
+ from enact.policies.git import no_push_to_main, require_branch_prefix
37
+
38
+ enact = EnactClient(
39
+ systems={"github": GitHubConnector(token="...")},
40
+ policies=[no_push_to_main, require_branch_prefix(prefix="agent/")],
41
+ workflows=[agent_pr_workflow],
42
+ )
43
+
44
+ result, receipt = enact.run(
45
+ workflow="agent_pr_workflow",
46
+ actor_email="agent@company.com",
47
+ payload={"repo": "owner/repo", "branch": "agent/my-feature"},
48
+ )
49
+ ```
50
+
51
+ ---
52
+
53
+ ## How It Works
54
+
55
+ ```
56
+ agent calls enact.run()
57
+
58
+
59
+ ┌───────────────────┐
60
+ │ Policy Gate │ All policies run. Any failure = BLOCK.
61
+ │ (pure Python, │ No LLMs. Versioned in Git. Testable.
62
+ │ no LLMs) │
63
+ └────────┬──────────┘
64
+ PASS │ BLOCK
65
+ │ └──▶ Receipt (decision=BLOCK, actions_taken=[])
66
+
67
+ ┌───────────────────┐
68
+ │ Workflow runs │ Enact executes the workflow against real systems.
69
+ │ against real │ Each action produces an ActionResult.
70
+ │ systems │
71
+ └────────┬──────────┘
72
+
73
+
74
+ ┌───────────────────┐
75
+ │ Signed Receipt │ HMAC-SHA256 signed. Captures who/what/why/
76
+ │ │ pass-fail/what changed.
77
+ └────────┬──────────┘
78
+
79
+
80
+ RunResult returned to agent
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Three Things Enact Gives You
86
+
87
+ 1. **Vetted action allowlist** — agents can only call workflows you explicitly register
88
+ 2. **Deterministic policy engine** — plain Python functions, no LLMs, Git-versioned, fully testable
89
+ 3. **Human-readable receipts** — every run records who, what, why, pass/fail, and what changed
90
+
91
+ ---
92
+
93
+ ## What Enact Can Do Right Now
94
+
95
+ ### Policy enforcement
96
+ - Block agents from pushing directly to `main` or `master`
97
+ - Require branch names to match a prefix (e.g. `agent/`)
98
+ - Cap how many files an agent can touch per commit
99
+ - Restrict actions to a UTC time window (e.g. 2am–6am maintenance window), including midnight-crossing windows like 22:00–06:00
100
+ - Block contractors from writing to PII fields
101
+ - Require the actor to hold a specific role (`admin`, `engineer`, etc.)
102
+ - Prevent duplicate contacts from being created in HubSpot (live lookup before the workflow runs)
103
+ - Rate-limit how many tasks an agent creates per contact within a rolling time window
104
+
105
+ ### GitHub operations (via `GitHubConnector`)
106
+ - Create a branch
107
+ - Open a pull request
108
+ - Create an issue
109
+ - Delete a branch
110
+ - Merge a pull request
111
+
112
+ Every method is allowlisted at construction time — `GitHubConnector(token=..., allowlist=["create_branch", "create_pr"])` means the connector will refuse to call any method not on the list, even if the workflow tries.
113
+
114
+ ### Built-in workflows
115
+ - **`agent_pr_workflow`** — creates a feature branch then opens a PR; aborts cleanly if branch creation fails so you never get a PR pointing at a non-existent branch
116
+ - **`db_safe_insert`** — checks for a duplicate row before inserting; returns an explanatory failure instead of letting the database raise a constraint violation
117
+
118
+ ### Receipts
119
+ Every run — pass or block — produces an HMAC-SHA256 signed JSON receipt written to `receipts/`. It captures: who ran what, the full payload, every policy result with its reason, the final decision, and a timestamp. `verify_signature()` lets you prove a receipt hasn't been tampered with after the fact.
120
+
121
+ ---
122
+
123
+ ## File Structure
124
+
125
+ ```
126
+ enact/
127
+ ├── enact/ # pip-installable package
128
+ │ ├── __init__.py # exports: EnactClient, all models
129
+ │ ├── models.py # data shapes for every object in a run
130
+ │ ├── client.py # EnactClient — orchestrates the full run() loop
131
+ │ ├── policy.py # policy engine — runs all checks, returns PolicyResult list
132
+ │ ├── receipt.py # builds, HMAC-signs, verifies, and writes receipts
133
+ │ ├── connectors/
134
+ │ │ ├── github.py # GitHub: create_branch, create_pr, create_issue, delete_branch, merge_pr
135
+ │ │ └── postgres.py # Postgres: insert_row, update_row, select_rows, delete_row (planned v0.2)
136
+ │ ├── workflows/
137
+ │ │ ├── agent_pr_workflow.py # create branch → open PR (never to main)
138
+ │ │ └── db_safe_insert.py # check constraints → insert row
139
+ │ └── policies/
140
+ │ ├── git.py # no_push_to_main, max_files_per_commit, require_branch_prefix
141
+ │ ├── crm.py # no_duplicate_contacts, limit_tasks_per_contact
142
+ │ ├── access.py # contractor_cannot_write_pii, require_actor_role
143
+ │ └── time.py # within_maintenance_window
144
+ ├── tests/
145
+ │ ├── test_policy_engine.py
146
+ │ ├── test_receipt.py
147
+ │ ├── test_client.py
148
+ │ ├── test_github.py
149
+ │ ├── test_git_policies.py
150
+ │ ├── test_policies.py
151
+ │ └── test_workflows.py
152
+ ├── examples/
153
+ │ └── quickstart.py # runnable demo — runs PASS then BLOCK, prints receipt
154
+ ├── receipts/ # auto-created at runtime, gitignored
155
+ └── pyproject.toml # PyPI config
156
+ ```
157
+
158
+ ### What each file does
159
+
160
+ | File | Job |
161
+ |------|-----|
162
+ | `models.py` | Defines data shapes. `WorkflowContext` (inputs), `PolicyResult` (one policy check), `ActionResult` (one workflow action), `Receipt` (full signed run record), `RunResult` (what the agent gets back). |
163
+ | `client.py` | The main entry point. `EnactClient.run()` builds context, runs policies, executes the workflow if PASS, writes the receipt, returns `RunResult`. |
164
+ | `policy.py` | Runs every registered policy against `WorkflowContext`. Returns `list[PolicyResult]`. Never bails early — always runs all checks. |
165
+ | `receipt.py` | Takes policy results + action results, builds a `Receipt`, signs it with HMAC-SHA256, writes it to `receipts/`. |
166
+ | `connectors/` | Thin wrappers around vendor SDKs. Each connector exposes named actions (`create_branch`, `insert_row`, etc.) that workflows call. |
167
+ | `workflows/` | Python functions that orchestrate connector actions. Each workflow step produces an `ActionResult`. |
168
+ | `policies/` | Built-in reusable policy functions (ships with `pip install enact`). Each takes a `WorkflowContext` and returns a `PolicyResult`. |
169
+
170
+ ---
171
+
172
+ ## Data Flow (in code)
173
+
174
+ ```
175
+ enact.run(workflow="agent_pr_workflow", actor_email="agent@co.com", payload={"repo": "owner/repo", "branch": "agent/fix"})
176
+
177
+ ├─▶ WorkflowContext(workflow, actor_email, payload, systems)
178
+
179
+ ├─▶ policy_results = [
180
+ │ PolicyResult(policy="no_push_to_main", passed=True, reason="Branch is not main/master"),
181
+ │ PolicyResult(policy="require_branch_prefix", passed=True, reason="Branch 'agent/fix' has required prefix"),
182
+ │ ]
183
+
184
+ ├─▶ decision = PASS → execute workflow
185
+
186
+ ├─▶ actions_taken = [
187
+ │ ActionResult(action="create_branch", system="github", success=True, output={"branch": "agent/fix"}),
188
+ │ ActionResult(action="create_pr", system="github", success=True, output={"pr_number": 42, "url": "..."}),
189
+ │ ]
190
+
191
+ ├─▶ Receipt(run_id, workflow, actor_email, payload, policy_results,
192
+ │ decision="PASS", actions_taken, timestamp, signature)
193
+
194
+ └─▶ RunResult(success=True, workflow="agent_pr_workflow", output={...})
195
+ ```
196
+
197
+ ---
198
+
199
+ ## Connectors (v0.1)
200
+
201
+ | System | Actions | Status |
202
+ |--------|---------|--------|
203
+ | GitHub | `create_branch`, `create_pr`, `push_commit`, `delete_branch`, `create_issue`, `merge_pr` | ✅ v0.1 |
204
+ | Postgres | `insert_row`, `update_row`, `select_rows`, `delete_row` | 🔜 v0.2 |
205
+ | HubSpot | `create_contact`, `update_deal`, `create_task`, `get_contact` | 🔜 v0.2 |
206
+
207
+ GitHub connector works with any repo accessible via a personal access token or GitHub App.
208
+
209
+ ---
210
+
211
+ ## Built-in Policies (v0.1)
212
+
213
+ | File | Policy | What it blocks |
214
+ |------|--------|----------------|
215
+ | `git.py` | `no_push_to_main` | Any direct push to main/master |
216
+ | `git.py` | `max_files_per_commit` | Commits touching too many files (blast radius) |
217
+ | `git.py` | `require_branch_prefix` | Agent branches not prefixed correctly |
218
+ | `crm.py` | `no_duplicate_contacts` | Creating a contact that already exists |
219
+ | `crm.py` | `limit_tasks_per_contact` | Too many tasks created in a time window |
220
+ | `access.py` | `contractor_cannot_write_pii` | Contractors writing PII fields |
221
+ | `access.py` | `require_actor_role` | Actors without an allowed role |
222
+ | `time.py` | `within_maintenance_window` | Actions outside allowed UTC time windows |
223
+
224
+ ---
225
+
226
+ ## Quickstart
227
+
228
+ ```bash
229
+ git clone https://github.com/russellmiller3/enact
230
+ cd enact
231
+ pip install -e ".[dev]"
232
+ python examples/quickstart.py
233
+ ```
234
+
235
+ ---
236
+
237
+ ## Run Tests
238
+
239
+ ```bash
240
+ pytest tests/ -v
241
+ # 96 tests, 0 failures
242
+ ```
243
+
244
+ ---
245
+
246
+ ## Environment Variables
247
+
248
+ | Variable | Default | Purpose |
249
+ |----------|---------|---------|
250
+ | `ENACT_SECRET` | `enact-default-secret` | HMAC signing key for receipts. Override in production. |
251
+ | `GITHUB_TOKEN` | — | GitHub PAT for GitHubConnector |
252
+
253
+ ---
254
+
255
+ ## License
256
+
257
+ [Elastic License 2.0 (ELv2)](./LICENSE)
258
+
259
+ Free to use, modify, and redistribute. You may **not** offer Enact as a hosted
260
+ or managed service, nor sell or resell the software itself as a product.
261
+ See [LICENSE](./LICENSE) for full terms.
@@ -0,0 +1,236 @@
1
+ # Enact
2
+
3
+ **An action firewall for AI agents.**
4
+
5
+ Enact sits between your AI agent and the outside world. Every action goes through a policy gate first. If it passes, Enact executes it and returns a signed receipt. If it doesn't, nothing happens.
6
+
7
+ ```python
8
+ from enact import EnactClient
9
+ from enact.connectors.github import GitHubConnector
10
+ from enact.workflows.agent_pr_workflow import agent_pr_workflow
11
+ from enact.policies.git import no_push_to_main, require_branch_prefix
12
+
13
+ enact = EnactClient(
14
+ systems={"github": GitHubConnector(token="...")},
15
+ policies=[no_push_to_main, require_branch_prefix(prefix="agent/")],
16
+ workflows=[agent_pr_workflow],
17
+ )
18
+
19
+ result, receipt = enact.run(
20
+ workflow="agent_pr_workflow",
21
+ actor_email="agent@company.com",
22
+ payload={"repo": "owner/repo", "branch": "agent/my-feature"},
23
+ )
24
+ ```
25
+
26
+ ---
27
+
28
+ ## How It Works
29
+
30
+ ```
31
+ agent calls enact.run()
32
+
33
+
34
+ ┌───────────────────┐
35
+ │ Policy Gate │ All policies run. Any failure = BLOCK.
36
+ │ (pure Python, │ No LLMs. Versioned in Git. Testable.
37
+ │ no LLMs) │
38
+ └────────┬──────────┘
39
+ PASS │ BLOCK
40
+ │ └──▶ Receipt (decision=BLOCK, actions_taken=[])
41
+
42
+ ┌───────────────────┐
43
+ │ Workflow runs │ Enact executes the workflow against real systems.
44
+ │ against real │ Each action produces an ActionResult.
45
+ │ systems │
46
+ └────────┬──────────┘
47
+
48
+
49
+ ┌───────────────────┐
50
+ │ Signed Receipt │ HMAC-SHA256 signed. Captures who/what/why/
51
+ │ │ pass-fail/what changed.
52
+ └────────┬──────────┘
53
+
54
+
55
+ RunResult returned to agent
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Three Things Enact Gives You
61
+
62
+ 1. **Vetted action allowlist** — agents can only call workflows you explicitly register
63
+ 2. **Deterministic policy engine** — plain Python functions, no LLMs, Git-versioned, fully testable
64
+ 3. **Human-readable receipts** — every run records who, what, why, pass/fail, and what changed
65
+
66
+ ---
67
+
68
+ ## What Enact Can Do Right Now
69
+
70
+ ### Policy enforcement
71
+ - Block agents from pushing directly to `main` or `master`
72
+ - Require branch names to match a prefix (e.g. `agent/`)
73
+ - Cap how many files an agent can touch per commit
74
+ - Restrict actions to a UTC time window (e.g. 2am–6am maintenance window), including midnight-crossing windows like 22:00–06:00
75
+ - Block contractors from writing to PII fields
76
+ - Require the actor to hold a specific role (`admin`, `engineer`, etc.)
77
+ - Prevent duplicate contacts from being created in HubSpot (live lookup before the workflow runs)
78
+ - Rate-limit how many tasks an agent creates per contact within a rolling time window
79
+
80
+ ### GitHub operations (via `GitHubConnector`)
81
+ - Create a branch
82
+ - Open a pull request
83
+ - Create an issue
84
+ - Delete a branch
85
+ - Merge a pull request
86
+
87
+ Every method is allowlisted at construction time — `GitHubConnector(token=..., allowlist=["create_branch", "create_pr"])` means the connector will refuse to call any method not on the list, even if the workflow tries.
88
+
89
+ ### Built-in workflows
90
+ - **`agent_pr_workflow`** — creates a feature branch then opens a PR; aborts cleanly if branch creation fails so you never get a PR pointing at a non-existent branch
91
+ - **`db_safe_insert`** — checks for a duplicate row before inserting; returns an explanatory failure instead of letting the database raise a constraint violation
92
+
93
+ ### Receipts
94
+ Every run — pass or block — produces an HMAC-SHA256 signed JSON receipt written to `receipts/`. It captures: who ran what, the full payload, every policy result with its reason, the final decision, and a timestamp. `verify_signature()` lets you prove a receipt hasn't been tampered with after the fact.
95
+
96
+ ---
97
+
98
+ ## File Structure
99
+
100
+ ```
101
+ enact/
102
+ ├── enact/ # pip-installable package
103
+ │ ├── __init__.py # exports: EnactClient, all models
104
+ │ ├── models.py # data shapes for every object in a run
105
+ │ ├── client.py # EnactClient — orchestrates the full run() loop
106
+ │ ├── policy.py # policy engine — runs all checks, returns PolicyResult list
107
+ │ ├── receipt.py # builds, HMAC-signs, verifies, and writes receipts
108
+ │ ├── connectors/
109
+ │ │ ├── github.py # GitHub: create_branch, create_pr, create_issue, delete_branch, merge_pr
110
+ │ │ └── postgres.py # Postgres: insert_row, update_row, select_rows, delete_row (planned v0.2)
111
+ │ ├── workflows/
112
+ │ │ ├── agent_pr_workflow.py # create branch → open PR (never to main)
113
+ │ │ └── db_safe_insert.py # check constraints → insert row
114
+ │ └── policies/
115
+ │ ├── git.py # no_push_to_main, max_files_per_commit, require_branch_prefix
116
+ │ ├── crm.py # no_duplicate_contacts, limit_tasks_per_contact
117
+ │ ├── access.py # contractor_cannot_write_pii, require_actor_role
118
+ │ └── time.py # within_maintenance_window
119
+ ├── tests/
120
+ │ ├── test_policy_engine.py
121
+ │ ├── test_receipt.py
122
+ │ ├── test_client.py
123
+ │ ├── test_github.py
124
+ │ ├── test_git_policies.py
125
+ │ ├── test_policies.py
126
+ │ └── test_workflows.py
127
+ ├── examples/
128
+ │ └── quickstart.py # runnable demo — runs PASS then BLOCK, prints receipt
129
+ ├── receipts/ # auto-created at runtime, gitignored
130
+ └── pyproject.toml # PyPI config
131
+ ```
132
+
133
+ ### What each file does
134
+
135
+ | File | Job |
136
+ |------|-----|
137
+ | `models.py` | Defines data shapes. `WorkflowContext` (inputs), `PolicyResult` (one policy check), `ActionResult` (one workflow action), `Receipt` (full signed run record), `RunResult` (what the agent gets back). |
138
+ | `client.py` | The main entry point. `EnactClient.run()` builds context, runs policies, executes the workflow if PASS, writes the receipt, returns `RunResult`. |
139
+ | `policy.py` | Runs every registered policy against `WorkflowContext`. Returns `list[PolicyResult]`. Never bails early — always runs all checks. |
140
+ | `receipt.py` | Takes policy results + action results, builds a `Receipt`, signs it with HMAC-SHA256, writes it to `receipts/`. |
141
+ | `connectors/` | Thin wrappers around vendor SDKs. Each connector exposes named actions (`create_branch`, `insert_row`, etc.) that workflows call. |
142
+ | `workflows/` | Python functions that orchestrate connector actions. Each workflow step produces an `ActionResult`. |
143
+ | `policies/` | Built-in reusable policy functions (ships with `pip install enact`). Each takes a `WorkflowContext` and returns a `PolicyResult`. |
144
+
145
+ ---
146
+
147
+ ## Data Flow (in code)
148
+
149
+ ```
150
+ enact.run(workflow="agent_pr_workflow", actor_email="agent@co.com", payload={"repo": "owner/repo", "branch": "agent/fix"})
151
+
152
+ ├─▶ WorkflowContext(workflow, actor_email, payload, systems)
153
+
154
+ ├─▶ policy_results = [
155
+ │ PolicyResult(policy="no_push_to_main", passed=True, reason="Branch is not main/master"),
156
+ │ PolicyResult(policy="require_branch_prefix", passed=True, reason="Branch 'agent/fix' has required prefix"),
157
+ │ ]
158
+
159
+ ├─▶ decision = PASS → execute workflow
160
+
161
+ ├─▶ actions_taken = [
162
+ │ ActionResult(action="create_branch", system="github", success=True, output={"branch": "agent/fix"}),
163
+ │ ActionResult(action="create_pr", system="github", success=True, output={"pr_number": 42, "url": "..."}),
164
+ │ ]
165
+
166
+ ├─▶ Receipt(run_id, workflow, actor_email, payload, policy_results,
167
+ │ decision="PASS", actions_taken, timestamp, signature)
168
+
169
+ └─▶ RunResult(success=True, workflow="agent_pr_workflow", output={...})
170
+ ```
171
+
172
+ ---
173
+
174
+ ## Connectors (v0.1)
175
+
176
+ | System | Actions | Status |
177
+ |--------|---------|--------|
178
+ | GitHub | `create_branch`, `create_pr`, `push_commit`, `delete_branch`, `create_issue`, `merge_pr` | ✅ v0.1 |
179
+ | Postgres | `insert_row`, `update_row`, `select_rows`, `delete_row` | 🔜 v0.2 |
180
+ | HubSpot | `create_contact`, `update_deal`, `create_task`, `get_contact` | 🔜 v0.2 |
181
+
182
+ GitHub connector works with any repo accessible via a personal access token or GitHub App.
183
+
184
+ ---
185
+
186
+ ## Built-in Policies (v0.1)
187
+
188
+ | File | Policy | What it blocks |
189
+ |------|--------|----------------|
190
+ | `git.py` | `no_push_to_main` | Any direct push to main/master |
191
+ | `git.py` | `max_files_per_commit` | Commits touching too many files (blast radius) |
192
+ | `git.py` | `require_branch_prefix` | Agent branches not prefixed correctly |
193
+ | `crm.py` | `no_duplicate_contacts` | Creating a contact that already exists |
194
+ | `crm.py` | `limit_tasks_per_contact` | Too many tasks created in a time window |
195
+ | `access.py` | `contractor_cannot_write_pii` | Contractors writing PII fields |
196
+ | `access.py` | `require_actor_role` | Actors without an allowed role |
197
+ | `time.py` | `within_maintenance_window` | Actions outside allowed UTC time windows |
198
+
199
+ ---
200
+
201
+ ## Quickstart
202
+
203
+ ```bash
204
+ git clone https://github.com/russellmiller3/enact
205
+ cd enact
206
+ pip install -e ".[dev]"
207
+ python examples/quickstart.py
208
+ ```
209
+
210
+ ---
211
+
212
+ ## Run Tests
213
+
214
+ ```bash
215
+ pytest tests/ -v
216
+ # 96 tests, 0 failures
217
+ ```
218
+
219
+ ---
220
+
221
+ ## Environment Variables
222
+
223
+ | Variable | Default | Purpose |
224
+ |----------|---------|---------|
225
+ | `ENACT_SECRET` | `enact-default-secret` | HMAC signing key for receipts. Override in production. |
226
+ | `GITHUB_TOKEN` | — | GitHub PAT for GitHubConnector |
227
+
228
+ ---
229
+
230
+ ## License
231
+
232
+ [Elastic License 2.0 (ELv2)](./LICENSE)
233
+
234
+ Free to use, modify, and redistribute. You may **not** offer Enact as a hosted
235
+ or managed service, nor sell or resell the software itself as a product.
236
+ See [LICENSE](./LICENSE) for full terms.
@@ -0,0 +1,19 @@
1
+ """Enact — an action firewall for AI agents."""
2
+
3
+ from enact.client import EnactClient
4
+ from enact.models import (
5
+ WorkflowContext,
6
+ PolicyResult,
7
+ ActionResult,
8
+ Receipt,
9
+ RunResult,
10
+ )
11
+
12
+ __all__ = [
13
+ "EnactClient",
14
+ "WorkflowContext",
15
+ "PolicyResult",
16
+ "ActionResult",
17
+ "Receipt",
18
+ "RunResult",
19
+ ]