petfishframework 0.3.1__tar.gz → 0.3.3__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.
- {petfishframework-0.3.1 → petfishframework-0.3.3}/CHANGELOG.md +33 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/PKG-INFO +3 -3
- {petfishframework-0.3.1 → petfishframework-0.3.3}/README.md +2 -2
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/api.md +152 -1
- {petfishframework-0.3.1 → petfishframework-0.3.3}/pyproject.toml +1 -1
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/__init__.py +1 -1
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/core/environment.py +4 -1
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/core/session.py +16 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/credentials/broker.py +34 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/credentials/token.py +28 -3
- petfishframework-0.3.3/src/petfishframework/policies/conditions.py +436 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/policies/engine.py +4 -1
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/policies/rule.py +4 -1
- petfishframework-0.3.3/src/petfishframework/policies/validator.py +100 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/tools/base.py +1 -1
- petfishframework-0.3.3/tests/test_credential_broker.py +231 -0
- petfishframework-0.3.3/tests/test_yaml_policy.py +650 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/uv.lock +1 -1
- petfishframework-0.3.1/src/petfishframework/policies/conditions.py +0 -176
- petfishframework-0.3.1/tests/test_credential_broker.py +0 -81
- petfishframework-0.3.1/tests/test_yaml_policy.py +0 -325
- {petfishframework-0.3.1 → petfishframework-0.3.3}/.env.example +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/.github/workflows/ci.yml +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/.github/workflows/publish.yml +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/.gitignore +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/.sisyphus/plans/v0.1.6-plan.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/.sisyphus/plans/v0.2.0-plan.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/.sisyphus/plans/v0.3.0-plan.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/AGENTS.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/AGENTS.md.new +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/CONTRIBUTING.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/LICENSE +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/SECURITY.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/conftest.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/architecture.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/benchmark-results.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/development-plan.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/development.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/enterprise-expense-demo.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/feedbacks/archives/petfishframework_0_1_4_playground_recommendations.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/feedbacks/archives/petfishframework_playground_report.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/feedbacks/archives/petfishframework_production_readiness_roadmap.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/feedbacks/archives/petfishframework_recommendations.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/feedbacks/archives/petfishframework_v0_1_6_playground_review.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/feedbacks/archives/petfishframework_v0_1_7_review.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/feedbacks/archives/petfishframework_v0_1_8_review.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/feedbacks/archives/petfishframework_v0_2_1_review.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/feedbacks/archives/petfishframework_v0_2_2_review.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/feedbacks/petfishframework_v0_3_0_review.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/release-checklist.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/research/competitor-analysis/README.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/research/competitor-analysis/competitor-matrix.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/research/competitor-analysis/market-brief.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/research/competitor-analysis/positioning-map.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/research/competitor-analysis/swot-analysis.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/research/literature-review/inclusion-exclusion-criteria.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/research/literature-review/literature-matrix.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/research/literature-review/literature-review.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/research/literature-review/search-strategy.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/research/reference-repos-absorption.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/skeleton-completeness-checklist.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/test-results-report.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/usage-guide.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/validation-roadmap.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/initialization-report.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/mcp/README.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/mcp/connection-checklist.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/mcp/mcp-config.example.json +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/opencode.json +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/qa/code-review-checklist.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/qa/qa-review.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/qa/qc-gate-decision.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/qa/test-plan.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/config.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/core/__init__.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/core/agent.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/core/compiled.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/core/contracts.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/core/conversation.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/core/events.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/core/structured.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/core/types.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/credentials/__init__.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/mcp/__init__.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/mcp/client.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/mcp/server.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/mcp/stdio_transport.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/mcp/wrapper.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/models/__init__.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/models/anthropic.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/models/fake.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/models/openai.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/observability/__init__.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/observability/sinks.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/permissions/__init__.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/permissions/model.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/policies/__init__.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/policies/test_runner.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reasoning/__init__.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reasoning/lats.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reasoning/llm_plus_p.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reasoning/react.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reliability/__init__.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reliability/audit_report.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reliability/cost.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reliability/cost_report.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reliability/pass_at_k.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reliability/replay.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reliability/retry.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reliability/timeout.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/retrieval/__init__.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/retrieval/adaptive.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/retrieval/crag.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/retrieval/memory_store.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/tools/__init__.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/tools/agent_tool.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/tools/calculator.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/tools/path_planner.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/tools/registry.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/tools/word_sorter.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tasks/backlog.md +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/integration/__init__.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/integration/test_anthropic_integration.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/integration/test_openai_integration.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/policies/enterprise-expense.tests.yaml +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_agent_tool.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_anthropic_adapter.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_api_agent_lifecycle.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_api_edge_cases.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_api_integration.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_api_public_surface.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_api_type_safety.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_async.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_audit_report_v2.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_conversation.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_credential_integration.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_degrade.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_degrade_failclosed.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_enterprise_demo.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_m2_m3_m4.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_mcp.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_openai_adapter.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_pass_at_k.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_permission_semantics.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_quickstart_smoke.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_replay.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_retrieval.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_retry.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_skeleton.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_stdio_transport.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_streaming.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_structured.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_tool_registry.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_v016_semantics.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_v018.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_v019.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_v2_interface_compatibility.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_v2_lats.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_v2_llm_plus_p.py +0 -0
- {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_yaml_policy_suite.py +0 -0
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to petfishFramework will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.3.2] — 2026-07-08
|
|
6
|
+
|
|
7
|
+
### YAML Policy DSL Expansion
|
|
8
|
+
- 13 new condition matchers (total 20): `amount_eq/gte/lte`, generic `field_eq` prefix, `role_count_gte`, `clearance_eq`, `tenant_id_eq`, `classification_eq`, `tags_contains`, `risk_level_eq`, `capabilities_contains`, `requires_credentials`, `session_risk_gt`, `prompt_risk_gt`
|
|
9
|
+
- AND/OR/NOT combinators (`any`/`all`/`not`, nestable in `when:` blocks)
|
|
10
|
+
- Policy schema validation (`policies/validator.py`): validates version, name, rules, effect values
|
|
11
|
+
|
|
12
|
+
### CredentialBroker Phase 2
|
|
13
|
+
- `BaseTool.credential_name` field — decouple tool name from credential name
|
|
14
|
+
- `ScopedToken.max_uses` + use counting — one-time tokens (`max_uses=1`)
|
|
15
|
+
- `ScopedToken.uses_remaining` property + `use()` method
|
|
16
|
+
- `revoke_all_for_tool(tool_name)` — revoke tokens for a specific tool
|
|
17
|
+
- `revoke_all()` — revoke everything (session-end cleanup)
|
|
18
|
+
- `active_token_count` property + `list_active_tokens()`
|
|
19
|
+
- Environment uses `credential_name` mapping when injecting tokens
|
|
20
|
+
- Session-end credential cleanup
|
|
21
|
+
|
|
22
|
+
## [0.3.1] — 2026-07-08
|
|
23
|
+
|
|
24
|
+
### Credential Event Safety (P0 security fix)
|
|
25
|
+
- Event log no longer stores `ScopedToken` objects — replaced with `{"credential_ref": ..., "tool_name": ..., "redacted": True}`
|
|
26
|
+
- Untrusted event sinks cannot access `.get_secret()` via event introspection
|
|
27
|
+
|
|
28
|
+
### Agent/Session credential_broker API
|
|
29
|
+
- `Agent(credential_broker=broker)` — first-class parameter
|
|
30
|
+
- `Session` passes broker to `RuntimeEnvironment`
|
|
31
|
+
- `RuntimeEnvironment` field renamed `_credential_broker` → `credential_broker`
|
|
32
|
+
|
|
33
|
+
### Documentation Sync
|
|
34
|
+
- `docs/api.md` version updated
|
|
35
|
+
- README roadmap: v0.3.x (current)
|
|
36
|
+
- README: added YAML Policy Engine + CredentialBroker usage examples
|
|
37
|
+
|
|
5
38
|
## [0.3.0] — 2026-07-08
|
|
6
39
|
|
|
7
40
|
### v0.3.0 Policy Engine + Credential Broker Integration
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: petfishframework
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: A lightweight runtime framework for reliable, auditable, budget-aware, and permission-aware AI agents.
|
|
5
5
|
Project-URL: Homepage, https://github.com/kylecui/petfishFramework
|
|
6
6
|
Project-URL: Documentation, https://github.com/kylecui/petfishFramework/tree/master/docs
|
|
@@ -34,7 +34,7 @@ Description-Content-Type: text/markdown
|
|
|
34
34
|
|
|
35
35
|
[](https://www.python.org/downloads/)
|
|
36
36
|
[](https://github.com/kylecui/petfishFramework/blob/master/LICENSE)
|
|
37
|
-
[](https://github.com/kylecui/petfishFramework/tree/master/tests/)
|
|
38
38
|
|
|
39
39
|
**Status: Alpha** — API may change. Core runtime works; see [Roadmap](#roadmap).
|
|
40
40
|
|
|
@@ -279,7 +279,7 @@ petfishFramework is **Alpha**. API may change before v1.0.
|
|
|
279
279
|
git clone https://github.com/kylecui/petfishFramework.git
|
|
280
280
|
cd petfishFramework
|
|
281
281
|
uv sync --all-extras
|
|
282
|
-
uv run pytest #
|
|
282
|
+
uv run pytest # 284 tests
|
|
283
283
|
uv run ruff check src/ tests/
|
|
284
284
|
```
|
|
285
285
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.python.org/downloads/)
|
|
6
6
|
[](https://github.com/kylecui/petfishFramework/blob/master/LICENSE)
|
|
7
|
-
[](https://github.com/kylecui/petfishFramework/tree/master/tests/)
|
|
8
8
|
|
|
9
9
|
**Status: Alpha** — API may change. Core runtime works; see [Roadmap](#roadmap).
|
|
10
10
|
|
|
@@ -249,7 +249,7 @@ petfishFramework is **Alpha**. API may change before v1.0.
|
|
|
249
249
|
git clone https://github.com/kylecui/petfishFramework.git
|
|
250
250
|
cd petfishFramework
|
|
251
251
|
uv sync --all-extras
|
|
252
|
-
uv run pytest #
|
|
252
|
+
uv run pytest # 284 tests
|
|
253
253
|
uv run ruff check src/ tests/
|
|
254
254
|
```
|
|
255
255
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# petfishFramework API Reference
|
|
2
2
|
|
|
3
|
-
This document is the authoritative reference for the public API of `petfishFramework` v0.3.
|
|
3
|
+
This document is the authoritative reference for the public API of `petfishFramework` v0.3.2. Every signature, field, and example below is derived from the source code and from the tests that exercise it.
|
|
4
4
|
|
|
5
5
|
## 1. Overview
|
|
6
6
|
|
|
@@ -1294,3 +1294,154 @@ decision.policy_name # "enterprise-expense-policy"
|
|
|
1294
1294
|
```
|
|
1295
1295
|
|
|
1296
1296
|
This lets audit logs trace each decision back to the policy file version that produced it.
|
|
1297
|
+
|
|
1298
|
+
### AND/OR/NOT Combinators (v0.3.2)
|
|
1299
|
+
|
|
1300
|
+
`when:` blocks support `any`/`all`/`not` combinators:
|
|
1301
|
+
|
|
1302
|
+
```yaml
|
|
1303
|
+
when:
|
|
1304
|
+
any:
|
|
1305
|
+
- action.tool_name: approve_payment
|
|
1306
|
+
- action.tool_name: transfer_funds
|
|
1307
|
+
|
|
1308
|
+
when:
|
|
1309
|
+
not:
|
|
1310
|
+
subject.role_in: [admin]
|
|
1311
|
+
|
|
1312
|
+
when:
|
|
1313
|
+
not:
|
|
1314
|
+
any:
|
|
1315
|
+
- subject.role_in: [admin]
|
|
1316
|
+
- subject.clearance_eq: secret
|
|
1317
|
+
```
|
|
1318
|
+
|
|
1319
|
+
### Full matcher list (v0.3.2)
|
|
1320
|
+
|
|
1321
|
+
| Key | Description | Example |
|
|
1322
|
+
|---|---|---|
|
|
1323
|
+
| `action.tool_name` | Exact tool name match | `approve_payment` |
|
|
1324
|
+
| `subject.role_in` | Any role in list matches | `[finance, admin]` |
|
|
1325
|
+
| `subject.role_not_in` | No role in list matches | `[intern]` |
|
|
1326
|
+
| `subject.role_count_gte` | Subject has ≥ N roles | `2` |
|
|
1327
|
+
| `subject.clearance_eq` | Clearance level match | `confidential` |
|
|
1328
|
+
| `subject.tenant_id_eq` | Tenant ID match | `acme-corp` |
|
|
1329
|
+
| `action.args.amount_gt` | `args["amount"] > value` | `5000` |
|
|
1330
|
+
| `action.args.amount_lt` | `args["amount"] < value` | `100` |
|
|
1331
|
+
| `action.args.amount_eq` | `args["amount"] == value` | `500` |
|
|
1332
|
+
| `action.args.amount_gte` | `args["amount"] >= value` | `1000` |
|
|
1333
|
+
| `action.args.amount_lte` | `args["amount"] <= value` | `200` |
|
|
1334
|
+
| `action.args.<field>_eq` | Generic field equality (prefix matcher) | `recipient_eq: alice@` |
|
|
1335
|
+
| `resource.classification_eq` | Resource classification match | `internal` |
|
|
1336
|
+
| `resource.tags_contains` | Resource has any of the listed tags | `[pii, sensitive]` |
|
|
1337
|
+
| `tool.side_effect` | Match tool metadata | `true` |
|
|
1338
|
+
| `tool.external_egress` | Match tool metadata | `true` |
|
|
1339
|
+
| `tool.requires_credentials` | Match tool metadata | `true` |
|
|
1340
|
+
| `tool.risk_level_eq` | Match risk level | `high` |
|
|
1341
|
+
| `tool.capabilities_contains` | Tool has any of listed capabilities | `[network, fs:write]` |
|
|
1342
|
+
| `context.session_risk_gt` | `context.session_risk > value` | `0.5` |
|
|
1343
|
+
| `context.prompt_risk_gt` | `context.prompt_risk > value` | `0.7` |
|
|
1344
|
+
|
|
1345
|
+
Unknown condition keys are fail-closed (return `False`).
|
|
1346
|
+
|
|
1347
|
+
### Policy schema validation (v0.3.2)
|
|
1348
|
+
|
|
1349
|
+
```python
|
|
1350
|
+
from petfishframework.policies.validator import validate_policy, validate_policy_file
|
|
1351
|
+
|
|
1352
|
+
errors = validate_policy_file("enterprise-expense.yaml")
|
|
1353
|
+
if errors:
|
|
1354
|
+
for err in errors:
|
|
1355
|
+
print(f" - {err}")
|
|
1356
|
+
```
|
|
1357
|
+
|
|
1358
|
+
Checks: version exists, name exists, rules is a list, each rule has name + valid effect, priority is integer.
|
|
1359
|
+
|
|
1360
|
+
---
|
|
1361
|
+
|
|
1362
|
+
## 16. Credential Broker
|
|
1363
|
+
|
|
1364
|
+
### CredentialBroker
|
|
1365
|
+
|
|
1366
|
+
```python
|
|
1367
|
+
from petfishframework.credentials import CredentialBroker
|
|
1368
|
+
|
|
1369
|
+
broker = CredentialBroker(default_ttl_s=3600)
|
|
1370
|
+
broker.register_credential("github_app", os.environ["GITHUB_TOKEN"])
|
|
1371
|
+
broker.register_credential("db_connection", "postgres://...")
|
|
1372
|
+
```
|
|
1373
|
+
|
|
1374
|
+
| Method | Description |
|
|
1375
|
+
|---|---|
|
|
1376
|
+
| `register_credential(name, secret)` | Register a real credential |
|
|
1377
|
+
| `issue_token(name, tool_name, ttl_s=None, max_uses=0)` | Issue a scoped, time-limited token |
|
|
1378
|
+
| `validate_token(token_id)` | Check if token is valid (not expired, not revoked) |
|
|
1379
|
+
| `revoke_token(token_id)` | Revoke a single token |
|
|
1380
|
+
| `revoke_all_for_tool(tool_name)` | Revoke all tokens for a specific tool (v0.3.2) |
|
|
1381
|
+
| `revoke_all()` | Revoke all active tokens (session-end cleanup) (v0.3.2) |
|
|
1382
|
+
| `cleanup_expired()` | Remove expired tokens, returns count removed |
|
|
1383
|
+
| `active_token_count` | Property: number of active tokens (v0.3.2) |
|
|
1384
|
+
| `list_active_tokens()` | Return list of active token_ids (v0.3.2) |
|
|
1385
|
+
|
|
1386
|
+
### ScopedToken
|
|
1387
|
+
|
|
1388
|
+
```python
|
|
1389
|
+
token = broker.issue_token("github_app", tool_name="github_tool", max_uses=1)
|
|
1390
|
+
token.token_id # unique ID
|
|
1391
|
+
token.tool_name # "github_tool"
|
|
1392
|
+
token.is_valid() # True (not expired, uses remaining)
|
|
1393
|
+
token.get_secret() # returns the actual secret (consumes one use if max_uses set)
|
|
1394
|
+
token.uses_remaining # remaining uses (v0.3.2)
|
|
1395
|
+
```
|
|
1396
|
+
|
|
1397
|
+
`__repr__` and `__str__` never expose the secret:
|
|
1398
|
+
```python
|
|
1399
|
+
repr(token) # "ScopedToken(token_id='abc...', tool_name='github_tool', ...)"
|
|
1400
|
+
str(token) # "ScopedToken(abc..., tool=github_tool, valid=True)"
|
|
1401
|
+
```
|
|
1402
|
+
|
|
1403
|
+
### Agent Integration
|
|
1404
|
+
|
|
1405
|
+
```python
|
|
1406
|
+
agent = Agent(
|
|
1407
|
+
model=model,
|
|
1408
|
+
reasoning=ReAct(),
|
|
1409
|
+
tools=tools,
|
|
1410
|
+
credential_broker=broker,
|
|
1411
|
+
)
|
|
1412
|
+
# Tools with requires_credentials=True receive a ScopedToken in args["_credential_token"]
|
|
1413
|
+
```
|
|
1414
|
+
|
|
1415
|
+
### Tool credential_name (v0.3.2)
|
|
1416
|
+
|
|
1417
|
+
```python
|
|
1418
|
+
@dataclass
|
|
1419
|
+
class GitHubTool(BaseTool):
|
|
1420
|
+
name: str = "github_create_issue"
|
|
1421
|
+
requires_credentials: bool = True
|
|
1422
|
+
credential_name: str = "github_app" # maps to broker credential name
|
|
1423
|
+
```
|
|
1424
|
+
|
|
1425
|
+
### Event Log Safety
|
|
1426
|
+
|
|
1427
|
+
ScopedToken objects are **never** stored in event data. The Environment replaces them with:
|
|
1428
|
+
|
|
1429
|
+
```python
|
|
1430
|
+
{"credential_ref": "token_id", "tool_name": "github_tool", "redacted": True}
|
|
1431
|
+
```
|
|
1432
|
+
|
|
1433
|
+
### One-time Tokens (v0.3.2)
|
|
1434
|
+
|
|
1435
|
+
```python
|
|
1436
|
+
token = broker.issue_token("secret_key", tool_name="signing_tool", max_uses=1)
|
|
1437
|
+
token.get_secret() # works, uses_remaining becomes 0
|
|
1438
|
+
token.get_secret() # raises ValueError("max uses exceeded")
|
|
1439
|
+
```
|
|
1440
|
+
|
|
1441
|
+
### Session-End Cleanup
|
|
1442
|
+
|
|
1443
|
+
```python
|
|
1444
|
+
# In Session._finalize_run():
|
|
1445
|
+
if session.credential_broker:
|
|
1446
|
+
session.credential_broker.cleanup_expired()
|
|
1447
|
+
```
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "petfishframework"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.3"
|
|
8
8
|
description = "A lightweight runtime framework for reliable, auditable, budget-aware, and permission-aware AI agents."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -334,7 +334,10 @@ class RuntimeEnvironment(Environment):
|
|
|
334
334
|
return
|
|
335
335
|
if self.credential_broker is None:
|
|
336
336
|
return
|
|
337
|
-
|
|
337
|
+
credential_name = getattr(tool, "credential_name", None) or tool.name
|
|
338
|
+
token = self.credential_broker.issue_token(
|
|
339
|
+
credential_name, tool_name=tool.name, max_uses=1
|
|
340
|
+
)
|
|
338
341
|
args["_credential_token"] = token
|
|
339
342
|
|
|
340
343
|
def _prepare_tool_call(self, ref: ToolRef, args: dict) -> tuple[Tool | None, Any]:
|
|
@@ -133,6 +133,7 @@ class Session:
|
|
|
133
133
|
self._result = result
|
|
134
134
|
|
|
135
135
|
self._save_conversation_history(result)
|
|
136
|
+
self._cleanup_credentials()
|
|
136
137
|
|
|
137
138
|
self.events.emit(
|
|
138
139
|
"session.end",
|
|
@@ -150,6 +151,21 @@ class Session:
|
|
|
150
151
|
)
|
|
151
152
|
return result
|
|
152
153
|
|
|
154
|
+
def _cleanup_credentials(self) -> None:
|
|
155
|
+
"""Clean up scoped credentials at session end."""
|
|
156
|
+
broker = self.credential_broker
|
|
157
|
+
if broker is None:
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
cleanup_expired = getattr(broker, "cleanup_expired", None)
|
|
161
|
+
if callable(cleanup_expired):
|
|
162
|
+
cleanup_expired()
|
|
163
|
+
|
|
164
|
+
revoke_all_for_tool = getattr(broker, "revoke_all_for_tool", None)
|
|
165
|
+
if callable(revoke_all_for_tool):
|
|
166
|
+
for tool in self.tools:
|
|
167
|
+
revoke_all_for_tool(tool.name)
|
|
168
|
+
|
|
153
169
|
def _save_conversation_history(self, result: Result) -> None:
|
|
154
170
|
"""Persist previous history plus the current user/assistant turn."""
|
|
155
171
|
if self.conversation_store is None or self.conversation_id is None:
|
{petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/credentials/broker.py
RENAMED
|
@@ -28,6 +28,7 @@ class CredentialBroker:
|
|
|
28
28
|
name: str,
|
|
29
29
|
tool_name: str,
|
|
30
30
|
ttl_s: float | None = None,
|
|
31
|
+
max_uses: int = 0,
|
|
31
32
|
) -> ScopedToken:
|
|
32
33
|
"""Issue a scoped, time-limited token for a specific tool."""
|
|
33
34
|
if name not in self._credentials:
|
|
@@ -42,6 +43,7 @@ class CredentialBroker:
|
|
|
42
43
|
token_id=token_id,
|
|
43
44
|
tool_name=tool_name,
|
|
44
45
|
expires_at=expires_at,
|
|
46
|
+
max_uses=max_uses,
|
|
45
47
|
_secret=secret,
|
|
46
48
|
)
|
|
47
49
|
self._active_tokens[token_id] = token
|
|
@@ -58,6 +60,38 @@ class CredentialBroker:
|
|
|
58
60
|
"""Revoke a token immediately."""
|
|
59
61
|
self._active_tokens.pop(token_id, None)
|
|
60
62
|
|
|
63
|
+
def revoke_all_for_tool(self, tool_name: str) -> int:
|
|
64
|
+
"""Revoke all active tokens issued for a specific tool.
|
|
65
|
+
|
|
66
|
+
Returns the number of tokens revoked.
|
|
67
|
+
"""
|
|
68
|
+
token_ids = [
|
|
69
|
+
token_id
|
|
70
|
+
for token_id, token in self._active_tokens.items()
|
|
71
|
+
if token.tool_name == tool_name
|
|
72
|
+
]
|
|
73
|
+
for token_id in token_ids:
|
|
74
|
+
del self._active_tokens[token_id]
|
|
75
|
+
return len(token_ids)
|
|
76
|
+
|
|
77
|
+
def revoke_all(self) -> int:
|
|
78
|
+
"""Revoke all active tokens.
|
|
79
|
+
|
|
80
|
+
Returns the number of tokens revoked.
|
|
81
|
+
"""
|
|
82
|
+
count = len(self._active_tokens)
|
|
83
|
+
self._active_tokens.clear()
|
|
84
|
+
return count
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def active_token_count(self) -> int:
|
|
88
|
+
"""Return the number of currently active tokens."""
|
|
89
|
+
return len(self._active_tokens)
|
|
90
|
+
|
|
91
|
+
def list_active_tokens(self) -> list[str]:
|
|
92
|
+
"""Return token IDs for all active tokens (secrets are not exposed)."""
|
|
93
|
+
return list(self._active_tokens.keys())
|
|
94
|
+
|
|
61
95
|
def cleanup_expired(self) -> int:
|
|
62
96
|
"""Remove all expired tokens. Returns count removed."""
|
|
63
97
|
expired = [
|
|
@@ -5,7 +5,7 @@ import time
|
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
@dataclass
|
|
8
|
+
@dataclass
|
|
9
9
|
class ScopedToken:
|
|
10
10
|
"""A time-limited, tool-scoped credential token.
|
|
11
11
|
|
|
@@ -15,23 +15,48 @@ class ScopedToken:
|
|
|
15
15
|
token_id: str # unique ID
|
|
16
16
|
tool_name: str # which tool this token is for
|
|
17
17
|
expires_at: float # unix timestamp
|
|
18
|
+
max_uses: int = 0 # 0 = unlimited uses
|
|
18
19
|
_secret: str = field(default="", repr=False, compare=False) # hidden from repr
|
|
20
|
+
_uses: int = field(default=0, repr=False)
|
|
19
21
|
|
|
20
22
|
def is_valid(self) -> bool:
|
|
21
23
|
"""Check if token is still valid (not expired)."""
|
|
22
24
|
return time.time() < self.expires_at
|
|
23
25
|
|
|
26
|
+
def use(self) -> bool:
|
|
27
|
+
"""Consume one use of the token.
|
|
28
|
+
|
|
29
|
+
Returns True if the use is allowed, False if max_uses exceeded.
|
|
30
|
+
"""
|
|
31
|
+
self._uses += 1
|
|
32
|
+
if self.max_uses > 0 and self._uses > self.max_uses:
|
|
33
|
+
return False
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def uses_remaining(self) -> int | None:
|
|
38
|
+
"""Remaining allowed uses, or None when unlimited."""
|
|
39
|
+
if self.max_uses <= 0:
|
|
40
|
+
return None
|
|
41
|
+
return max(0, self.max_uses - self._uses)
|
|
42
|
+
|
|
24
43
|
def get_secret(self) -> str:
|
|
25
44
|
"""Get the actual secret. Only callable by trusted code."""
|
|
26
45
|
if not self.is_valid():
|
|
27
46
|
raise ValueError("Token expired")
|
|
47
|
+
if not self.use():
|
|
48
|
+
raise ValueError("Token exceeded max uses")
|
|
28
49
|
return self._secret
|
|
29
50
|
|
|
30
51
|
def __repr__(self) -> str:
|
|
31
52
|
return (
|
|
32
53
|
f"ScopedToken(token_id={self.token_id!r}, tool_name={self.tool_name!r}, "
|
|
33
|
-
f"expires_at={self.expires_at},
|
|
54
|
+
f"expires_at={self.expires_at}, max_uses={self.max_uses}, "
|
|
55
|
+
f"_secret='[REDACTED]')"
|
|
34
56
|
)
|
|
35
57
|
|
|
36
58
|
def __str__(self) -> str:
|
|
37
|
-
return
|
|
59
|
+
return (
|
|
60
|
+
f"ScopedToken({self.token_id}, tool={self.tool_name}, "
|
|
61
|
+
f"valid={self.is_valid()}, uses_remaining={self.uses_remaining})"
|
|
62
|
+
)
|