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.
Files changed (160) hide show
  1. {petfishframework-0.3.1 → petfishframework-0.3.3}/CHANGELOG.md +33 -0
  2. {petfishframework-0.3.1 → petfishframework-0.3.3}/PKG-INFO +3 -3
  3. {petfishframework-0.3.1 → petfishframework-0.3.3}/README.md +2 -2
  4. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/api.md +152 -1
  5. {petfishframework-0.3.1 → petfishframework-0.3.3}/pyproject.toml +1 -1
  6. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/__init__.py +1 -1
  7. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/core/environment.py +4 -1
  8. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/core/session.py +16 -0
  9. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/credentials/broker.py +34 -0
  10. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/credentials/token.py +28 -3
  11. petfishframework-0.3.3/src/petfishframework/policies/conditions.py +436 -0
  12. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/policies/engine.py +4 -1
  13. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/policies/rule.py +4 -1
  14. petfishframework-0.3.3/src/petfishframework/policies/validator.py +100 -0
  15. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/tools/base.py +1 -1
  16. petfishframework-0.3.3/tests/test_credential_broker.py +231 -0
  17. petfishframework-0.3.3/tests/test_yaml_policy.py +650 -0
  18. {petfishframework-0.3.1 → petfishframework-0.3.3}/uv.lock +1 -1
  19. petfishframework-0.3.1/src/petfishframework/policies/conditions.py +0 -176
  20. petfishframework-0.3.1/tests/test_credential_broker.py +0 -81
  21. petfishframework-0.3.1/tests/test_yaml_policy.py +0 -325
  22. {petfishframework-0.3.1 → petfishframework-0.3.3}/.env.example +0 -0
  23. {petfishframework-0.3.1 → petfishframework-0.3.3}/.github/workflows/ci.yml +0 -0
  24. {petfishframework-0.3.1 → petfishframework-0.3.3}/.github/workflows/publish.yml +0 -0
  25. {petfishframework-0.3.1 → petfishframework-0.3.3}/.gitignore +0 -0
  26. {petfishframework-0.3.1 → petfishframework-0.3.3}/.sisyphus/plans/v0.1.6-plan.md +0 -0
  27. {petfishframework-0.3.1 → petfishframework-0.3.3}/.sisyphus/plans/v0.2.0-plan.md +0 -0
  28. {petfishframework-0.3.1 → petfishframework-0.3.3}/.sisyphus/plans/v0.3.0-plan.md +0 -0
  29. {petfishframework-0.3.1 → petfishframework-0.3.3}/AGENTS.md +0 -0
  30. {petfishframework-0.3.1 → petfishframework-0.3.3}/AGENTS.md.new +0 -0
  31. {petfishframework-0.3.1 → petfishframework-0.3.3}/CONTRIBUTING.md +0 -0
  32. {petfishframework-0.3.1 → petfishframework-0.3.3}/LICENSE +0 -0
  33. {petfishframework-0.3.1 → petfishframework-0.3.3}/SECURITY.md +0 -0
  34. {petfishframework-0.3.1 → petfishframework-0.3.3}/conftest.py +0 -0
  35. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/architecture.md +0 -0
  36. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/benchmark-results.md +0 -0
  37. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/development-plan.md +0 -0
  38. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/development.md +0 -0
  39. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/enterprise-expense-demo.md +0 -0
  40. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/feedbacks/archives/petfishframework_0_1_4_playground_recommendations.md +0 -0
  41. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/feedbacks/archives/petfishframework_playground_report.md +0 -0
  42. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/feedbacks/archives/petfishframework_production_readiness_roadmap.md +0 -0
  43. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/feedbacks/archives/petfishframework_recommendations.md +0 -0
  44. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/feedbacks/archives/petfishframework_v0_1_6_playground_review.md +0 -0
  45. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/feedbacks/archives/petfishframework_v0_1_7_review.md +0 -0
  46. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/feedbacks/archives/petfishframework_v0_1_8_review.md +0 -0
  47. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/feedbacks/archives/petfishframework_v0_2_1_review.md +0 -0
  48. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/feedbacks/archives/petfishframework_v0_2_2_review.md +0 -0
  49. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/feedbacks/petfishframework_v0_3_0_review.md +0 -0
  50. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/release-checklist.md +0 -0
  51. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/research/competitor-analysis/README.md +0 -0
  52. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/research/competitor-analysis/competitor-matrix.md +0 -0
  53. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/research/competitor-analysis/market-brief.md +0 -0
  54. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/research/competitor-analysis/positioning-map.md +0 -0
  55. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/research/competitor-analysis/swot-analysis.md +0 -0
  56. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/research/literature-review/inclusion-exclusion-criteria.md +0 -0
  57. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/research/literature-review/literature-matrix.md +0 -0
  58. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/research/literature-review/literature-review.md +0 -0
  59. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/research/literature-review/search-strategy.md +0 -0
  60. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/research/reference-repos-absorption.md +0 -0
  61. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/skeleton-completeness-checklist.md +0 -0
  62. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/test-results-report.md +0 -0
  63. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/usage-guide.md +0 -0
  64. {petfishframework-0.3.1 → petfishframework-0.3.3}/docs/validation-roadmap.md +0 -0
  65. {petfishframework-0.3.1 → petfishframework-0.3.3}/initialization-report.md +0 -0
  66. {petfishframework-0.3.1 → petfishframework-0.3.3}/mcp/README.md +0 -0
  67. {petfishframework-0.3.1 → petfishframework-0.3.3}/mcp/connection-checklist.md +0 -0
  68. {petfishframework-0.3.1 → petfishframework-0.3.3}/mcp/mcp-config.example.json +0 -0
  69. {petfishframework-0.3.1 → petfishframework-0.3.3}/opencode.json +0 -0
  70. {petfishframework-0.3.1 → petfishframework-0.3.3}/qa/code-review-checklist.md +0 -0
  71. {petfishframework-0.3.1 → petfishframework-0.3.3}/qa/qa-review.md +0 -0
  72. {petfishframework-0.3.1 → petfishframework-0.3.3}/qa/qc-gate-decision.md +0 -0
  73. {petfishframework-0.3.1 → petfishframework-0.3.3}/qa/test-plan.md +0 -0
  74. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/config.py +0 -0
  75. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/core/__init__.py +0 -0
  76. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/core/agent.py +0 -0
  77. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/core/compiled.py +0 -0
  78. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/core/contracts.py +0 -0
  79. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/core/conversation.py +0 -0
  80. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/core/events.py +0 -0
  81. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/core/structured.py +0 -0
  82. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/core/types.py +0 -0
  83. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/credentials/__init__.py +0 -0
  84. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/mcp/__init__.py +0 -0
  85. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/mcp/client.py +0 -0
  86. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/mcp/server.py +0 -0
  87. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/mcp/stdio_transport.py +0 -0
  88. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/mcp/wrapper.py +0 -0
  89. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/models/__init__.py +0 -0
  90. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/models/anthropic.py +0 -0
  91. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/models/fake.py +0 -0
  92. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/models/openai.py +0 -0
  93. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/observability/__init__.py +0 -0
  94. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/observability/sinks.py +0 -0
  95. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/permissions/__init__.py +0 -0
  96. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/permissions/model.py +0 -0
  97. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/policies/__init__.py +0 -0
  98. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/policies/test_runner.py +0 -0
  99. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reasoning/__init__.py +0 -0
  100. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reasoning/lats.py +0 -0
  101. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reasoning/llm_plus_p.py +0 -0
  102. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reasoning/react.py +0 -0
  103. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reliability/__init__.py +0 -0
  104. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reliability/audit_report.py +0 -0
  105. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reliability/cost.py +0 -0
  106. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reliability/cost_report.py +0 -0
  107. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reliability/pass_at_k.py +0 -0
  108. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reliability/replay.py +0 -0
  109. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reliability/retry.py +0 -0
  110. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/reliability/timeout.py +0 -0
  111. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/retrieval/__init__.py +0 -0
  112. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/retrieval/adaptive.py +0 -0
  113. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/retrieval/crag.py +0 -0
  114. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/retrieval/memory_store.py +0 -0
  115. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/tools/__init__.py +0 -0
  116. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/tools/agent_tool.py +0 -0
  117. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/tools/calculator.py +0 -0
  118. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/tools/path_planner.py +0 -0
  119. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/tools/registry.py +0 -0
  120. {petfishframework-0.3.1 → petfishframework-0.3.3}/src/petfishframework/tools/word_sorter.py +0 -0
  121. {petfishframework-0.3.1 → petfishframework-0.3.3}/tasks/backlog.md +0 -0
  122. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/integration/__init__.py +0 -0
  123. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/integration/test_anthropic_integration.py +0 -0
  124. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/integration/test_openai_integration.py +0 -0
  125. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/policies/enterprise-expense.tests.yaml +0 -0
  126. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_agent_tool.py +0 -0
  127. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_anthropic_adapter.py +0 -0
  128. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_api_agent_lifecycle.py +0 -0
  129. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_api_edge_cases.py +0 -0
  130. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_api_integration.py +0 -0
  131. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_api_public_surface.py +0 -0
  132. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_api_type_safety.py +0 -0
  133. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_async.py +0 -0
  134. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_audit_report_v2.py +0 -0
  135. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_conversation.py +0 -0
  136. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_credential_integration.py +0 -0
  137. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_degrade.py +0 -0
  138. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_degrade_failclosed.py +0 -0
  139. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_enterprise_demo.py +0 -0
  140. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_m2_m3_m4.py +0 -0
  141. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_mcp.py +0 -0
  142. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_openai_adapter.py +0 -0
  143. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_pass_at_k.py +0 -0
  144. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_permission_semantics.py +0 -0
  145. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_quickstart_smoke.py +0 -0
  146. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_replay.py +0 -0
  147. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_retrieval.py +0 -0
  148. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_retry.py +0 -0
  149. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_skeleton.py +0 -0
  150. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_stdio_transport.py +0 -0
  151. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_streaming.py +0 -0
  152. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_structured.py +0 -0
  153. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_tool_registry.py +0 -0
  154. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_v016_semantics.py +0 -0
  155. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_v018.py +0 -0
  156. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_v019.py +0 -0
  157. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_v2_interface_compatibility.py +0 -0
  158. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_v2_lats.py +0 -0
  159. {petfishframework-0.3.1 → petfishframework-0.3.3}/tests/test_v2_llm_plus_p.py +0 -0
  160. {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.1
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
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
36
36
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/kylecui/petfishFramework/blob/master/LICENSE)
37
- [![Tests: 259](https://img.shields.io/badge/tests-259-brightgreen.svg)](https://github.com/kylecui/petfishFramework/tree/master/tests/)
37
+ [![Tests: 284](https://img.shields.io/badge/tests-284-brightgreen.svg)](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 # 259 tests
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
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/kylecui/petfishFramework/blob/master/LICENSE)
7
- [![Tests: 259](https://img.shields.io/badge/tests-259-brightgreen.svg)](https://github.com/kylecui/petfishFramework/tree/master/tests/)
7
+ [![Tests: 284](https://img.shields.io/badge/tests-284-brightgreen.svg)](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 # 259 tests
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.1. Every signature, field, and example below is derived from the source code and from the tests that exercise it.
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.1"
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"
@@ -15,7 +15,7 @@ from .reasoning import LATS, LLMPlusP, ReAct
15
15
  from .reliability.replay import ReplayMode
16
16
  from .tools.base import BaseTool
17
17
 
18
- __version__ = "0.3.1"
18
+ __version__ = "0.3.3"
19
19
 
20
20
  __all__ = [
21
21
  "Agent",
@@ -334,7 +334,10 @@ class RuntimeEnvironment(Environment):
334
334
  return
335
335
  if self.credential_broker is None:
336
336
  return
337
- token = self.credential_broker.issue_token(tool.name, tool_name=tool.name)
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:
@@ -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(frozen=True)
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}, _secret='[REDACTED]')"
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 f"ScopedToken({self.token_id}, tool={self.tool_name}, valid={self.is_valid()})"
59
+ return (
60
+ f"ScopedToken({self.token_id}, tool={self.tool_name}, "
61
+ f"valid={self.is_valid()}, uses_remaining={self.uses_remaining})"
62
+ )