petfishframework 0.2.5__tar.gz → 0.3.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.
- petfishframework-0.3.0/.sisyphus/plans/v0.3.0-plan.md +340 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/CHANGELOG.md +11 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/PKG-INFO +7 -4
- {petfishframework-0.2.5 → petfishframework-0.3.0}/README.md +5 -3
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/api.md +82 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/pyproject.toml +4 -2
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/__init__.py +4 -1
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/core/environment.py +21 -0
- petfishframework-0.3.0/src/petfishframework/credentials/__init__.py +7 -0
- petfishframework-0.3.0/src/petfishframework/credentials/broker.py +70 -0
- petfishframework-0.3.0/src/petfishframework/credentials/token.py +37 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/permissions/model.py +2 -0
- petfishframework-0.3.0/src/petfishframework/policies/__init__.py +12 -0
- petfishframework-0.3.0/src/petfishframework/policies/conditions.py +176 -0
- petfishframework-0.3.0/src/petfishframework/policies/engine.py +130 -0
- petfishframework-0.3.0/src/petfishframework/policies/rule.py +32 -0
- petfishframework-0.3.0/src/petfishframework/policies/test_runner.py +167 -0
- petfishframework-0.3.0/tests/policies/enterprise-expense.tests.yaml +45 -0
- petfishframework-0.3.0/tests/test_credential_broker.py +81 -0
- petfishframework-0.3.0/tests/test_credential_integration.py +107 -0
- petfishframework-0.3.0/tests/test_yaml_policy.py +325 -0
- petfishframework-0.3.0/tests/test_yaml_policy_suite.py +81 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/uv.lock +73 -5
- {petfishframework-0.2.5 → petfishframework-0.3.0}/.env.example +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/.github/workflows/ci.yml +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/.github/workflows/publish.yml +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/.gitignore +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/.sisyphus/plans/v0.1.6-plan.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/.sisyphus/plans/v0.2.0-plan.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/AGENTS.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/AGENTS.md.new +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/CONTRIBUTING.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/LICENSE +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/SECURITY.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/conftest.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/architecture.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/benchmark-results.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/development-plan.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/development.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/enterprise-expense-demo.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/feedbacks/archives/petfishframework_0_1_4_playground_recommendations.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/feedbacks/archives/petfishframework_playground_report.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/feedbacks/archives/petfishframework_production_readiness_roadmap.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/feedbacks/archives/petfishframework_recommendations.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/feedbacks/archives/petfishframework_v0_1_6_playground_review.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/feedbacks/archives/petfishframework_v0_1_7_review.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/feedbacks/archives/petfishframework_v0_1_8_review.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/feedbacks/archives/petfishframework_v0_2_1_review.md +0 -0
- {petfishframework-0.2.5/docs/feedbacks → petfishframework-0.3.0/docs/feedbacks/archives}/petfishframework_v0_2_2_review.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/release-checklist.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/research/competitor-analysis/README.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/research/competitor-analysis/competitor-matrix.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/research/competitor-analysis/market-brief.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/research/competitor-analysis/positioning-map.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/research/competitor-analysis/swot-analysis.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/research/literature-review/inclusion-exclusion-criteria.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/research/literature-review/literature-matrix.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/research/literature-review/literature-review.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/research/literature-review/search-strategy.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/research/reference-repos-absorption.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/skeleton-completeness-checklist.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/test-results-report.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/usage-guide.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/docs/validation-roadmap.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/initialization-report.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/mcp/README.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/mcp/connection-checklist.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/mcp/mcp-config.example.json +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/opencode.json +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/qa/code-review-checklist.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/qa/qa-review.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/qa/qc-gate-decision.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/qa/test-plan.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/config.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/core/__init__.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/core/agent.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/core/compiled.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/core/contracts.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/core/conversation.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/core/events.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/core/session.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/core/structured.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/core/types.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/mcp/__init__.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/mcp/client.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/mcp/server.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/mcp/stdio_transport.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/mcp/wrapper.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/models/__init__.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/models/anthropic.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/models/fake.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/models/openai.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/observability/__init__.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/observability/sinks.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/permissions/__init__.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/reasoning/__init__.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/reasoning/lats.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/reasoning/llm_plus_p.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/reasoning/react.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/reliability/__init__.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/reliability/audit_report.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/reliability/cost.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/reliability/cost_report.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/reliability/pass_at_k.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/reliability/replay.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/reliability/retry.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/reliability/timeout.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/retrieval/__init__.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/retrieval/adaptive.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/retrieval/crag.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/retrieval/memory_store.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/tools/__init__.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/tools/agent_tool.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/tools/base.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/tools/calculator.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/tools/path_planner.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/tools/registry.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/src/petfishframework/tools/word_sorter.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tasks/backlog.md +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/integration/__init__.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/integration/test_anthropic_integration.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/integration/test_openai_integration.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_agent_tool.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_anthropic_adapter.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_api_agent_lifecycle.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_api_edge_cases.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_api_integration.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_api_public_surface.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_api_type_safety.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_async.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_audit_report_v2.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_conversation.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_degrade.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_degrade_failclosed.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_enterprise_demo.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_m2_m3_m4.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_mcp.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_openai_adapter.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_pass_at_k.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_permission_semantics.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_quickstart_smoke.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_replay.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_retrieval.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_retry.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_skeleton.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_stdio_transport.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_streaming.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_structured.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_tool_registry.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_v016_semantics.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_v018.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_v019.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_v2_interface_compatibility.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_v2_lats.py +0 -0
- {petfishframework-0.2.5 → petfishframework-0.3.0}/tests/test_v2_llm_plus_p.py +0 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# petfishFramework v0.3.0 开发计划
|
|
2
|
+
|
|
3
|
+
> 基于 v0.2.5 状态 + 6 轮用户反馈 + 生产可用路线图
|
|
4
|
+
> 目标:从代码策略进入配置化策略 + 凭证治理 MVP
|
|
5
|
+
> 原则:**TDD 先行,YAML-first,向后兼容**
|
|
6
|
+
|
|
7
|
+
## v0.3.0 使命
|
|
8
|
+
|
|
9
|
+
> 让安全/运维团队通过 YAML 文件配置 Agent 权限策略,无需写 Python 代码。
|
|
10
|
+
> 让 Agent 永不持有真实凭证——通过 CredentialBroker 签发 scoped 短期令牌。
|
|
11
|
+
|
|
12
|
+
## v0.2.5 → v0.3.0 状态跃迁
|
|
13
|
+
|
|
14
|
+
| 维度 | v0.2.5(当前) | v0.3.0(目标) |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| 策略定义 | Python class (PermissionPolicy) | **YAML 文件 + Python fallback** |
|
|
17
|
+
| 策略测试 | 手写 pytest | **YAML test suite + pytest runner** |
|
|
18
|
+
| 策略版本 | 无 | **policy_version 字段 + 审计追踪** |
|
|
19
|
+
| 凭证 | Agent 持有 API key | **CredentialBroker scoped 令牌** |
|
|
20
|
+
| 策略组合 | 单一 policy | **多规则 deny-overrides 合并** |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Phase A: YAML Policy Engine
|
|
25
|
+
|
|
26
|
+
### A1: YAML 策略解析器 + 匹配引擎
|
|
27
|
+
|
|
28
|
+
**目标**:从 YAML 文件加载策略规则,按优先级匹配,生成 Decision。
|
|
29
|
+
|
|
30
|
+
**文件**:`src/petfishframework/policies/` (新模块)
|
|
31
|
+
|
|
32
|
+
**YAML 格式**:
|
|
33
|
+
|
|
34
|
+
```yaml
|
|
35
|
+
version: "1.0"
|
|
36
|
+
name: "enterprise-expense-policy"
|
|
37
|
+
|
|
38
|
+
rules:
|
|
39
|
+
- name: deny-non-finance-approval
|
|
40
|
+
priority: 100
|
|
41
|
+
when:
|
|
42
|
+
action.tool_name: approve_payment
|
|
43
|
+
subject.role_not_in: [finance, admin]
|
|
44
|
+
effect: DENY
|
|
45
|
+
reason: "only finance/admin can approve payments"
|
|
46
|
+
|
|
47
|
+
- name: require-approval-for-side-effects
|
|
48
|
+
priority: 90
|
|
49
|
+
when:
|
|
50
|
+
tool.side_effect: true
|
|
51
|
+
effect: REQUIRE_APPROVAL
|
|
52
|
+
reason: "side-effect tool requires approval"
|
|
53
|
+
|
|
54
|
+
- name: degrade-large-payments
|
|
55
|
+
priority: 80
|
|
56
|
+
when:
|
|
57
|
+
action.tool_name: approve_payment
|
|
58
|
+
action.args.amount_gt: 5000
|
|
59
|
+
effect: DEGRADE
|
|
60
|
+
reason: "large payment degraded to dry-run"
|
|
61
|
+
fallback_tool: dry_run_payment
|
|
62
|
+
|
|
63
|
+
- name: mask-pii-in-payments
|
|
64
|
+
priority: 70
|
|
65
|
+
when:
|
|
66
|
+
action.tool_name: approve_payment
|
|
67
|
+
effect: MASK
|
|
68
|
+
reason: "mask PII"
|
|
69
|
+
input_mask_fields: [recipient]
|
|
70
|
+
output_mask_fields: [recipient]
|
|
71
|
+
event_mask_fields: [recipient]
|
|
72
|
+
|
|
73
|
+
- name: partial-allow-policy-check
|
|
74
|
+
priority: 60
|
|
75
|
+
when:
|
|
76
|
+
action.tool_name: check_policy
|
|
77
|
+
effect: PARTIAL_ALLOW
|
|
78
|
+
reason: "only amount needed"
|
|
79
|
+
allowed_fields: [amount]
|
|
80
|
+
|
|
81
|
+
- name: default-allow
|
|
82
|
+
priority: 0
|
|
83
|
+
when: {}
|
|
84
|
+
effect: ALLOW
|
|
85
|
+
reason: "default allow"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
#### TDD 测试先行
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
# tests/test_yaml_policy.py
|
|
92
|
+
|
|
93
|
+
def test_yaml_policy_loads_from_file():
|
|
94
|
+
"""YAML 文件成功加载为 PolicyEngine。"""
|
|
95
|
+
|
|
96
|
+
def test_yaml_policy_deny_rule_blocks():
|
|
97
|
+
"""deny-non-finance 规则阻断非 finance 用户。"""
|
|
98
|
+
|
|
99
|
+
def test_yaml_policy_priority_ordering():
|
|
100
|
+
"""高 priority 规则先匹配。"""
|
|
101
|
+
|
|
102
|
+
def test_yaml_policy_deny_overrides_allow():
|
|
103
|
+
"""deny 规则覆盖 default-allow。"""
|
|
104
|
+
|
|
105
|
+
def test_yaml_policy_mask_rule_sets_fields():
|
|
106
|
+
"""mask 规则正确设置 input/output/event mask fields。"""
|
|
107
|
+
|
|
108
|
+
def test_yaml_policy_degrade_rule_sets_fallback():
|
|
109
|
+
"""degrade 规则正确设置 fallback_tool。"""
|
|
110
|
+
|
|
111
|
+
def test_yaml_policy_partial_allow_sets_allowed_fields():
|
|
112
|
+
"""partial_allow 规则正确设置 allowed_fields。"""
|
|
113
|
+
|
|
114
|
+
def test_yaml_policy_no_match_returns_default():
|
|
115
|
+
"""无规则匹配时返回默认 Decision。"""
|
|
116
|
+
|
|
117
|
+
def test_yaml_policy_condition_amount_gt():
|
|
118
|
+
"""amount_gt 条件正确比较数值。"""
|
|
119
|
+
|
|
120
|
+
def test_yaml_policy_condition_role_not_in():
|
|
121
|
+
"""role_not_in 条件正确检查角色。"""
|
|
122
|
+
|
|
123
|
+
def test_yaml_policy_condition_tool_side_effect():
|
|
124
|
+
"""tool.side_effect 条件匹配工具元数据。"""
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
#### 实现
|
|
128
|
+
|
|
129
|
+
1. `policies/__init__.py` — 导出
|
|
130
|
+
2. `policies/rule.py` — `PolicyRule` dataclass (name, priority, when, effect, reason, mask_fields, fallback_tool, allowed_fields)
|
|
131
|
+
3. `policies/conditions.py` — 条件匹配器:
|
|
132
|
+
- `action.tool_name` — 精确匹配
|
|
133
|
+
- `action.args.amount_gt` — 数值大于
|
|
134
|
+
- `action.args.amount_lt` — 数值小于
|
|
135
|
+
- `subject.role_in` — 角色在列表中
|
|
136
|
+
- `subject.role_not_in` — 角色不在列表中
|
|
137
|
+
- `tool.side_effect` — 工具元数据匹配
|
|
138
|
+
- `tool.external_egress` — 工具元数据匹配
|
|
139
|
+
4. `policies/engine.py` — `YamlPolicy` class:
|
|
140
|
+
- `from_file(path) -> YamlPolicy`
|
|
141
|
+
- `from_string(yaml_str) -> YamlPolicy`
|
|
142
|
+
- `evaluate(subject, action, resource, context) -> Decision`
|
|
143
|
+
- 按 priority 降序遍历规则,deny-overrides 语义
|
|
144
|
+
- 需要工具元数据 → 接受 `tool_registry` 或在 evaluate 时接收 tool 对象
|
|
145
|
+
|
|
146
|
+
### A2: YAML 策略测试套件
|
|
147
|
+
|
|
148
|
+
**目标**:在 YAML 中定义测试用例,pytest 自动运行。
|
|
149
|
+
|
|
150
|
+
**YAML 格式**:
|
|
151
|
+
|
|
152
|
+
```yaml
|
|
153
|
+
# tests/policies/enterprise-expense.tests.yaml
|
|
154
|
+
policy_file: enterprise-expense.yaml
|
|
155
|
+
|
|
156
|
+
tests:
|
|
157
|
+
- name: analyst-cannot-approve
|
|
158
|
+
input:
|
|
159
|
+
subject.roles: [analyst]
|
|
160
|
+
action.tool_name: approve_payment
|
|
161
|
+
action.args.amount: 300
|
|
162
|
+
expect:
|
|
163
|
+
effect: DENY
|
|
164
|
+
|
|
165
|
+
- name: finance-small-payment-partial-allow
|
|
166
|
+
input:
|
|
167
|
+
subject.roles: [finance]
|
|
168
|
+
action.tool_name: check_policy
|
|
169
|
+
action.args.amount: 350
|
|
170
|
+
expect:
|
|
171
|
+
effect: PARTIAL_ALLOW
|
|
172
|
+
|
|
173
|
+
- name: large-payment-degrades
|
|
174
|
+
input:
|
|
175
|
+
subject.roles: [finance]
|
|
176
|
+
action.tool_name: approve_payment
|
|
177
|
+
action.args.amount: 8000
|
|
178
|
+
expect:
|
|
179
|
+
effect: REQUIRE_APPROVAL # side_effect 先匹配
|
|
180
|
+
|
|
181
|
+
- name: unknown-tool-default-allow
|
|
182
|
+
input:
|
|
183
|
+
subject.roles: [analyst]
|
|
184
|
+
action.tool_name: unknown_tool
|
|
185
|
+
expect:
|
|
186
|
+
effect: ALLOW
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### TDD 测试先行
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
# tests/test_yaml_policy_suite.py
|
|
193
|
+
|
|
194
|
+
def test_policy_suite_runs_all_tests():
|
|
195
|
+
"""YAML 测试套件中所有用例通过。"""
|
|
196
|
+
|
|
197
|
+
def test_policy_suite_reports_failures():
|
|
198
|
+
"""测试套件中失败的用例被报告。"""
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### 实现
|
|
202
|
+
|
|
203
|
+
- `policies/test_runner.py` — 加载 `.tests.yaml`,构造 Subject/Action/Resource/Context,调用 policy.evaluate,比较 effect
|
|
204
|
+
- pytest fixture 自动发现 `tests/policies/*.tests.yaml`
|
|
205
|
+
|
|
206
|
+
### A3: 策略版本 + 审计元数据
|
|
207
|
+
|
|
208
|
+
**目标**:策略带版本号,决策事件记录策略版本。
|
|
209
|
+
|
|
210
|
+
#### TDD 测试先行
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
def test_policy_has_version():
|
|
214
|
+
"""YAML policy 有 version 字段。"""
|
|
215
|
+
|
|
216
|
+
def test_decision_records_policy_version():
|
|
217
|
+
"""Decision 记录 policy_version。"""
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
#### 实现
|
|
221
|
+
|
|
222
|
+
- `PolicyRule` 增加 `version: str` 字段
|
|
223
|
+
- `YamlPolicy` 从 YAML `version` 读取
|
|
224
|
+
- `Decision` 增加 `policy_version: str | None = None` 字段
|
|
225
|
+
- `YamlPolicy.evaluate()` 在返回 Decision 时填充 policy_version
|
|
226
|
+
- Environment 事件记录 policy_version
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Phase B: CredentialBroker MVP
|
|
231
|
+
|
|
232
|
+
### B1: Scoped 令牌签发 + 过期
|
|
233
|
+
|
|
234
|
+
**目标**:Agent 永不持有真实 API key。CredentialBroker 签发短期限时令牌。
|
|
235
|
+
|
|
236
|
+
**文件**:`src/petfishframework/credentials/` (新模块)
|
|
237
|
+
|
|
238
|
+
#### TDD 测试先行
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
# tests/test_credential_broker.py
|
|
242
|
+
|
|
243
|
+
def test_broker_issues_scoped_token():
|
|
244
|
+
"""Broker 发放 scoped 令牌(限 tool + scope)。"""
|
|
245
|
+
|
|
246
|
+
def test_broker_token_has_ttl():
|
|
247
|
+
"""令牌有 TTL,过期后失效。"""
|
|
248
|
+
|
|
249
|
+
def test_broker_token_not_in_event_log():
|
|
250
|
+
"""令牌不出现在事件日志中。"""
|
|
251
|
+
|
|
252
|
+
def test_broker_revokes_token():
|
|
253
|
+
"""Broker 可以撤销令牌。"""
|
|
254
|
+
|
|
255
|
+
def test_broker_token_scoped_to_tool():
|
|
256
|
+
"""令牌限定了可调用的工具。"""
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### 实现
|
|
260
|
+
|
|
261
|
+
1. `credentials/__init__.py` — 导出
|
|
262
|
+
2. `credentials/broker.py` — `CredentialBroker`:
|
|
263
|
+
- `register_credential(name, secret, scope)` — 注册真实凭证
|
|
264
|
+
- `issue_token(name, tool_name, ttl_s) -> ScopedToken` — 签发限时令牌
|
|
265
|
+
- `validate_token(token) -> bool` — 验证令牌有效性
|
|
266
|
+
- `revoke_token(token_id)` — 撤销令牌
|
|
267
|
+
3. `credentials/token.py` — `ScopedToken` dataclass:
|
|
268
|
+
- `token_id: str` — 唯一 ID
|
|
269
|
+
- `tool_name: str` — 限定的工具
|
|
270
|
+
- `expires_at: float` — 过期时间戳
|
|
271
|
+
- `_secret: str` — 内部引用(不暴露在事件日志中)
|
|
272
|
+
- `is_valid() -> bool`
|
|
273
|
+
- `__repr__` 脱敏(显示 `[REDACTED]`)
|
|
274
|
+
|
|
275
|
+
### B2: 与 Environment 集成
|
|
276
|
+
|
|
277
|
+
**目标**:工具通过 Broker 获取凭证,不直接持有 API key。
|
|
278
|
+
|
|
279
|
+
#### 实现
|
|
280
|
+
|
|
281
|
+
- `Environment` 增加 `credential_broker: CredentialBroker | None`
|
|
282
|
+
- 工具的 `requires_credentials: True` 时,Environment 在 `call()` 前通过 Broker 签发令牌
|
|
283
|
+
- 令牌传递给工具 `execute()` 的 args 中(字段名 `_credential_token`)
|
|
284
|
+
- 事件日志中令牌值脱敏
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Phase C: 企业 PoC 集成 + 文档
|
|
289
|
+
|
|
290
|
+
### C1: 企业报销审批 YAML 策略
|
|
291
|
+
|
|
292
|
+
**目标**:用 YAML 文件替代 v0.2.x 的 Python ExpensePolicy。
|
|
293
|
+
|
|
294
|
+
**文件**:`examples/policies/enterprise-expense.yaml`
|
|
295
|
+
|
|
296
|
+
### C2: 企业 demo 升级
|
|
297
|
+
|
|
298
|
+
**目标**:`examples/05_enterprise_expense.py` 升级为 YAML 策略驱动。
|
|
299
|
+
|
|
300
|
+
### C3: 文档更新
|
|
301
|
+
|
|
302
|
+
- `docs/api.md` 增加 YAML Policy + CredentialBroker 章节
|
|
303
|
+
- `docs/usage-guide.md` 增加「YAML 策略配置」+「凭证治理」段落
|
|
304
|
+
- `CHANGELOG.md` v0.3.0
|
|
305
|
+
- `README.md` 更新 Current Limitations
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## 执行顺序与依赖
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
A1 (YAML engine) → A2 (test suite) → A3 (version metadata)
|
|
313
|
+
↓
|
|
314
|
+
B1 (Broker MVP) → B2 (Environment integration)
|
|
315
|
+
↓
|
|
316
|
+
C1 (YAML policy file) → C2 (demo upgrade) → C3 (docs)
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## 不做清单
|
|
320
|
+
|
|
321
|
+
- ❌ OpenTelemetry(v0.4.0)
|
|
322
|
+
- ❌ 确定性回放 RESUME/RERUN(v0.4.0)
|
|
323
|
+
- ❌ MCP server mode(v0.5.0)
|
|
324
|
+
- ❌ Vault 集成(v0.4.0+)
|
|
325
|
+
- ❌ Policy 热重载(v0.4.0+)
|
|
326
|
+
|
|
327
|
+
## 验收标准
|
|
328
|
+
|
|
329
|
+
| # | 标准 | 验证方法 |
|
|
330
|
+
|---|---|---|
|
|
331
|
+
| V1 | YAML policy 从文件加载并正确匹配 | `test_yaml_policy.py` 全通过 |
|
|
332
|
+
| V2 | 6 种 DecisionEffect 全部可从 YAML 配置 | YAML 规则覆盖全部 effect |
|
|
333
|
+
| V3 | deny-overrides 语义正确 | `test_yaml_policy_deny_overrides` |
|
|
334
|
+
| V4 | YAML test suite 自动运行 | `pytest tests/policies/` |
|
|
335
|
+
| V5 | CredentialBroker 签发 scoped 令牌 | `test_credential_broker.py` |
|
|
336
|
+
| V6 | 令牌不出现在事件日志 | `test_broker_token_not_in_event_log` |
|
|
337
|
+
| V7 | 企业 demo 使用 YAML 策略 | `python examples/05_enterprise_expense.py` |
|
|
338
|
+
| V8 | 所有 234+ 现有测试仍通过 | pytest |
|
|
339
|
+
| V9 | ruff clean | ruff check |
|
|
340
|
+
| V10 | Trusted Publishing 发布成功 | git tag v0.3.0 |
|
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to petfishFramework will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.3.0] — 2026-07-08
|
|
6
|
+
|
|
7
|
+
### v0.3.0 Policy Engine + Credential Broker Integration
|
|
8
|
+
|
|
9
|
+
- YAML Policy Engine (Phase A1): load authorization rules from YAML via `YamlPolicy.from_file` / `from_string`
|
|
10
|
+
- Condition matchers: `action.tool_name`, `subject.role_in`, `subject.role_not_in`, `action.args.amount_gt`, `action.args.amount_lt`, `tool.side_effect`, `tool.external_egress`
|
|
11
|
+
- Enterprise expense YAML policy example (`examples/policies/enterprise-expense.yaml`)
|
|
12
|
+
- `CredentialBroker` + `ScopedToken`: issue scoped, time-limited tokens that hide secrets in `repr` / `str`
|
|
13
|
+
- `RuntimeEnvironment` integration: tools with `requires_credentials=True` receive a scoped token via `_credential_token` before execution
|
|
14
|
+
- 3 new TDD tests covering credential injection, non-credential tools, and event-log safety
|
|
15
|
+
|
|
5
16
|
## [0.2.0] — 2026-07-08
|
|
6
17
|
|
|
7
18
|
### v0.2.0 Enterprise PoC Release
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: petfishframework
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
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
|
|
@@ -19,6 +19,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
20
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
21
21
|
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
22
23
|
Provides-Extra: anthropic
|
|
23
24
|
Requires-Dist: anthropic>=0.40; extra == 'anthropic'
|
|
24
25
|
Provides-Extra: mcp
|
|
@@ -33,7 +34,7 @@ Description-Content-Type: text/markdown
|
|
|
33
34
|
|
|
34
35
|
[](https://www.python.org/downloads/)
|
|
35
36
|
[](https://github.com/kylecui/petfishFramework/blob/master/LICENSE)
|
|
36
|
-
[](https://github.com/kylecui/petfishFramework/tree/master/tests/)
|
|
37
38
|
|
|
38
39
|
**Status: Alpha** — API may change. Core runtime works; see [Roadmap](#roadmap).
|
|
39
40
|
|
|
@@ -218,7 +219,7 @@ Run: `python examples/05_enterprise_expense.py`
|
|
|
218
219
|
|
|
219
220
|
- **v0.2.x** (current): Core runtime, permission semantics, enterprise PoC, Trusted Publishing ✅
|
|
220
221
|
- **v0.2.x**: Enterprise agent examples, structured audit reports
|
|
221
|
-
- **v0.3.x**: Policy engine (YAML), credential broker
|
|
222
|
+
- **v0.3.x**: Policy engine (YAML), credential broker ✅
|
|
222
223
|
- **v0.4.x**: Production hardening, deployment guides
|
|
223
224
|
|
|
224
225
|
## Current Limitations
|
|
@@ -239,6 +240,8 @@ petfishFramework is **Alpha**. API may change before v1.0.
|
|
|
239
240
|
| MCP client stdio | ✅ Available |
|
|
240
241
|
| MCP server mode | 📋 Planned |
|
|
241
242
|
| Structured output / conversation memory | ✅ Available |
|
|
243
|
+
| YAML Policy Engine | ✅ Available |
|
|
244
|
+
| Credential Broker | ✅ Available |
|
|
242
245
|
| LATS / LLM+P | ⚠️ Lightweight implementations |
|
|
243
246
|
| CRAG / Adaptive-RAG | ⚠️ Lightweight reference implementations |
|
|
244
247
|
|
|
@@ -248,7 +251,7 @@ petfishFramework is **Alpha**. API may change before v1.0.
|
|
|
248
251
|
git clone https://github.com/kylecui/petfishFramework.git
|
|
249
252
|
cd petfishFramework
|
|
250
253
|
uv sync --all-extras
|
|
251
|
-
uv run pytest #
|
|
254
|
+
uv run pytest # 259 tests
|
|
252
255
|
uv run ruff check src/ tests/
|
|
253
256
|
```
|
|
254
257
|
|
|
@@ -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
|
|
|
@@ -189,7 +189,7 @@ Run: `python examples/05_enterprise_expense.py`
|
|
|
189
189
|
|
|
190
190
|
- **v0.2.x** (current): Core runtime, permission semantics, enterprise PoC, Trusted Publishing ✅
|
|
191
191
|
- **v0.2.x**: Enterprise agent examples, structured audit reports
|
|
192
|
-
- **v0.3.x**: Policy engine (YAML), credential broker
|
|
192
|
+
- **v0.3.x**: Policy engine (YAML), credential broker ✅
|
|
193
193
|
- **v0.4.x**: Production hardening, deployment guides
|
|
194
194
|
|
|
195
195
|
## Current Limitations
|
|
@@ -210,6 +210,8 @@ petfishFramework is **Alpha**. API may change before v1.0.
|
|
|
210
210
|
| MCP client stdio | ✅ Available |
|
|
211
211
|
| MCP server mode | 📋 Planned |
|
|
212
212
|
| Structured output / conversation memory | ✅ Available |
|
|
213
|
+
| YAML Policy Engine | ✅ Available |
|
|
214
|
+
| Credential Broker | ✅ Available |
|
|
213
215
|
| LATS / LLM+P | ⚠️ Lightweight implementations |
|
|
214
216
|
| CRAG / Adaptive-RAG | ⚠️ Lightweight reference implementations |
|
|
215
217
|
|
|
@@ -219,7 +221,7 @@ petfishFramework is **Alpha**. API may change before v1.0.
|
|
|
219
221
|
git clone https://github.com/kylecui/petfishFramework.git
|
|
220
222
|
cd petfishFramework
|
|
221
223
|
uv sync --all-extras
|
|
222
|
-
uv run pytest #
|
|
224
|
+
uv run pytest # 259 tests
|
|
223
225
|
uv run ruff check src/ tests/
|
|
224
226
|
```
|
|
225
227
|
|
|
@@ -1212,3 +1212,85 @@ Tool functions that raise internally are caught by `BaseTool` and `MCPToolWrappe
|
|
|
1212
1212
|
### Replay divergence
|
|
1213
1213
|
|
|
1214
1214
|
`ReplayEnvironment` raises `RuntimeError` when a replayed strategy makes more calls than were recorded. This detects when the execution path has diverged from the original run. `ResumableEnvironment` switches to the live environment after the checkpoint; calling it beyond the checkpoint continues live execution and may produce a different answer.
|
|
1215
|
+
|
|
1216
|
+
## 15. YAML Policy Engine
|
|
1217
|
+
|
|
1218
|
+
Authorization rules can be loaded from YAML and evaluated through `YamlPolicy`, which implements the same `PermissionPolicy` protocol as custom Python policies.
|
|
1219
|
+
|
|
1220
|
+
```python
|
|
1221
|
+
from petfishframework import YamlPolicy
|
|
1222
|
+
|
|
1223
|
+
policy = YamlPolicy.from_file("policies/enterprise-expense.yaml")
|
|
1224
|
+
policy.register_tools(tools)
|
|
1225
|
+
|
|
1226
|
+
decision = policy.evaluate(subject, action, resource, context)
|
|
1227
|
+
print(decision.effect, decision.reason)
|
|
1228
|
+
```
|
|
1229
|
+
|
|
1230
|
+
### Loading policies
|
|
1231
|
+
|
|
1232
|
+
```python
|
|
1233
|
+
from petfishframework import YamlPolicy
|
|
1234
|
+
|
|
1235
|
+
policy = YamlPolicy.from_file("path/to/policy.yaml")
|
|
1236
|
+
policy = YamlPolicy.from_string(yaml_text)
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
`YamlPolicy.from_file` reads UTF-8 YAML and returns a policy instance. `register_tools(tools)` must be called if rules reference tool metadata such as `side_effect` or `external_egress`.
|
|
1240
|
+
|
|
1241
|
+
### Policy file format
|
|
1242
|
+
|
|
1243
|
+
```yaml
|
|
1244
|
+
version: "1.0"
|
|
1245
|
+
name: "enterprise-expense-policy"
|
|
1246
|
+
rules:
|
|
1247
|
+
- name: deny-non-finance-approval
|
|
1248
|
+
priority: 100
|
|
1249
|
+
when:
|
|
1250
|
+
action.tool_name: approve_payment
|
|
1251
|
+
subject.role_not_in: [finance, admin]
|
|
1252
|
+
effect: DENY
|
|
1253
|
+
reason: "only finance/admin can approve payments"
|
|
1254
|
+
|
|
1255
|
+
- name: require-approval-for-side-effects
|
|
1256
|
+
priority: 90
|
|
1257
|
+
when:
|
|
1258
|
+
tool.side_effect: true
|
|
1259
|
+
effect: REQUIRE_APPROVAL
|
|
1260
|
+
reason: "side-effect tool requires approval"
|
|
1261
|
+
|
|
1262
|
+
- name: default-allow
|
|
1263
|
+
priority: 0
|
|
1264
|
+
when: {}
|
|
1265
|
+
effect: ALLOW
|
|
1266
|
+
reason: "default allow"
|
|
1267
|
+
```
|
|
1268
|
+
|
|
1269
|
+
Rules are evaluated in descending `priority` order. The first fully matching rule wins. `version` and `name` are optional metadata carried on the resulting `Decision`.
|
|
1270
|
+
|
|
1271
|
+
### Condition matchers
|
|
1272
|
+
|
|
1273
|
+
Each key under `when:` selects a matcher. Matchers are fail-closed: an unknown key never matches.
|
|
1274
|
+
|
|
1275
|
+
| Condition key | Meaning | Example value |
|
|
1276
|
+
|---|---|---|
|
|
1277
|
+
| `action.tool_name` | Exact tool name match | `"approve_payment"` |
|
|
1278
|
+
| `subject.role_in` | Subject has any listed role | `[finance, admin]` |
|
|
1279
|
+
| `subject.role_not_in` | Subject has none of the listed roles | `[finance, admin]` |
|
|
1280
|
+
| `action.args.amount_gt` | `action.args["amount"] > value` | `5000` |
|
|
1281
|
+
| `action.args.amount_lt` | `action.args["amount"] < value` | `100` |
|
|
1282
|
+
| `tool.side_effect` | Match tool metadata flag | `true` |
|
|
1283
|
+
| `tool.external_egress` | Match tool metadata flag | `true` |
|
|
1284
|
+
|
|
1285
|
+
Compound conditions use AND semantics: every key in a `when:` block must match for the rule to apply.
|
|
1286
|
+
|
|
1287
|
+
### Policy version
|
|
1288
|
+
|
|
1289
|
+
The `version` field in YAML is exposed on every `Decision` produced by the policy:
|
|
1290
|
+
|
|
1291
|
+
```python
|
|
1292
|
+
decision.policy_version # "1.0"
|
|
1293
|
+
decision.policy_name # "enterprise-expense-policy"
|
|
1294
|
+
```
|
|
1295
|
+
|
|
1296
|
+
This lets audit logs trace each decision back to the policy file version that produced it.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "petfishframework"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
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"
|
|
@@ -21,7 +21,9 @@ classifiers = [
|
|
|
21
21
|
"Programming Language :: Python :: 3.12",
|
|
22
22
|
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
23
23
|
]
|
|
24
|
-
dependencies = [
|
|
24
|
+
dependencies = [
|
|
25
|
+
"pyyaml>=6.0.3",
|
|
26
|
+
]
|
|
25
27
|
|
|
26
28
|
[project.urls]
|
|
27
29
|
Homepage = "https://github.com/kylecui/petfishFramework"
|
|
@@ -10,11 +10,12 @@ from .core.agent import Agent
|
|
|
10
10
|
from .core.contracts import Tool
|
|
11
11
|
from .core.types import Budget, BudgetExceeded, Result, Task
|
|
12
12
|
from .permissions.model import DecisionEffect
|
|
13
|
+
from .policies import PolicyRule, YamlPolicy
|
|
13
14
|
from .reasoning import LATS, LLMPlusP, ReAct
|
|
14
15
|
from .reliability.replay import ReplayMode
|
|
15
16
|
from .tools.base import BaseTool
|
|
16
17
|
|
|
17
|
-
__version__ = "0.
|
|
18
|
+
__version__ = "0.3.0"
|
|
18
19
|
|
|
19
20
|
__all__ = [
|
|
20
21
|
"Agent",
|
|
@@ -25,8 +26,10 @@ __all__ = [
|
|
|
25
26
|
"FrameworkConfig",
|
|
26
27
|
"LATS",
|
|
27
28
|
"LLMPlusP",
|
|
29
|
+
"PolicyRule",
|
|
28
30
|
"ReAct",
|
|
29
31
|
"ReplayMode",
|
|
32
|
+
"YamlPolicy",
|
|
30
33
|
"Result",
|
|
31
34
|
"Task",
|
|
32
35
|
"Tool",
|
|
@@ -10,6 +10,7 @@ import copy
|
|
|
10
10
|
from dataclasses import dataclass
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
|
+
from petfishframework.credentials import CredentialBroker
|
|
13
14
|
from petfishframework.permissions.model import (
|
|
14
15
|
AccessContext,
|
|
15
16
|
Action,
|
|
@@ -79,6 +80,7 @@ class RuntimeEnvironment(Environment):
|
|
|
79
80
|
policy: PermissionPolicy
|
|
80
81
|
session_id: str = ""
|
|
81
82
|
_accountant: CostAccountant | None = None
|
|
83
|
+
_credential_broker: CredentialBroker | None = None
|
|
82
84
|
|
|
83
85
|
def __post_init__(self) -> None:
|
|
84
86
|
if self._accountant is None:
|
|
@@ -133,6 +135,7 @@ class RuntimeEnvironment(Environment):
|
|
|
133
135
|
effect, executed=False,
|
|
134
136
|
)
|
|
135
137
|
fallback_args = decision.fallback_args if decision.fallback_args is not None else args
|
|
138
|
+
self._maybe_inject_credential(fallback, fallback_args)
|
|
136
139
|
result = fallback.execute(fallback_args)
|
|
137
140
|
self.events.emit(
|
|
138
141
|
"tool.degraded",
|
|
@@ -176,6 +179,7 @@ class RuntimeEnvironment(Environment):
|
|
|
176
179
|
# Execute tool (ALLOW, PARTIAL_ALLOW, DEGRADE-without-fallback, MASK all execute)
|
|
177
180
|
import time as _time
|
|
178
181
|
|
|
182
|
+
self._maybe_inject_credential(tool, args)
|
|
179
183
|
start = _time.time()
|
|
180
184
|
try:
|
|
181
185
|
result = tool.execute(args)
|
|
@@ -221,6 +225,7 @@ class RuntimeEnvironment(Environment):
|
|
|
221
225
|
effect, executed=False,
|
|
222
226
|
)
|
|
223
227
|
fallback_args = decision.fallback_args if decision.fallback_args is not None else args
|
|
228
|
+
self._maybe_inject_credential(fallback, fallback_args)
|
|
224
229
|
if asyncio.iscoroutinefunction(fallback.execute):
|
|
225
230
|
result = await fallback.execute(fallback_args)
|
|
226
231
|
else:
|
|
@@ -263,6 +268,7 @@ class RuntimeEnvironment(Environment):
|
|
|
263
268
|
if effect == DecisionEffect.MASK and decision.input_mask_fields:
|
|
264
269
|
args = _apply_mask_to_dict(args, decision.input_mask_fields)
|
|
265
270
|
|
|
271
|
+
self._maybe_inject_credential(tool, args)
|
|
266
272
|
if asyncio.iscoroutinefunction(tool.execute):
|
|
267
273
|
result = await tool.execute(args)
|
|
268
274
|
else:
|
|
@@ -316,6 +322,21 @@ class RuntimeEnvironment(Environment):
|
|
|
316
322
|
return t
|
|
317
323
|
return None
|
|
318
324
|
|
|
325
|
+
def _maybe_inject_credential(self, tool: Tool, args: dict) -> None:
|
|
326
|
+
"""Issue and inject a scoped credential token when a tool requires one.
|
|
327
|
+
|
|
328
|
+
The token value is never materialized as a plain string inside the
|
|
329
|
+
environment; it is stored as a ScopedToken whose repr/str hide the
|
|
330
|
+
underlying secret. If no broker is configured, the tool proceeds
|
|
331
|
+
without credentials (graceful degradation).
|
|
332
|
+
"""
|
|
333
|
+
if not getattr(tool, "requires_credentials", False):
|
|
334
|
+
return
|
|
335
|
+
if self._credential_broker is None:
|
|
336
|
+
return
|
|
337
|
+
token = self._credential_broker.issue_token(tool.name, tool_name=tool.name)
|
|
338
|
+
args["_credential_token"] = token
|
|
339
|
+
|
|
319
340
|
def _prepare_tool_call(self, ref: ToolRef, args: dict) -> tuple[Tool | None, Any]:
|
|
320
341
|
"""Permission gate for tool calls; shared by sync and async paths."""
|
|
321
342
|
tool = self._find_tool(ref.name)
|