weaver-contracts 0.2.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.
- weaver_contracts-0.2.0/PKG-INFO +106 -0
- weaver_contracts-0.2.0/README.md +93 -0
- weaver_contracts-0.2.0/pyproject.toml +39 -0
- weaver_contracts-0.2.0/setup.cfg +4 -0
- weaver_contracts-0.2.0/src/weaver_contracts/__init__.py +38 -0
- weaver_contracts-0.2.0/src/weaver_contracts/core.py +287 -0
- weaver_contracts-0.2.0/src/weaver_contracts/extended.py +138 -0
- weaver_contracts-0.2.0/src/weaver_contracts/version.py +36 -0
- weaver_contracts-0.2.0/src/weaver_contracts.egg-info/PKG-INFO +106 -0
- weaver_contracts-0.2.0/src/weaver_contracts.egg-info/SOURCES.txt +14 -0
- weaver_contracts-0.2.0/src/weaver_contracts.egg-info/dependency_links.txt +1 -0
- weaver_contracts-0.2.0/src/weaver_contracts.egg-info/requires.txt +6 -0
- weaver_contracts-0.2.0/src/weaver_contracts.egg-info/top_level.txt +1 -0
- weaver_contracts-0.2.0/tests/test_extended.py +264 -0
- weaver_contracts-0.2.0/tests/test_json_schema_alignment.py +147 -0
- weaver_contracts-0.2.0/tests/test_roundtrip_examples.py +364 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: weaver_contracts
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Minimal Python contracts for the Weaver Stack (contextweaver, agent-kernel, ChainWeaver)
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Provides-Extra: dev
|
|
9
|
+
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
10
|
+
Requires-Dist: jsonschema>=4.17; extra == "dev"
|
|
11
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
12
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
13
|
+
|
|
14
|
+
# weaver_contracts Python Package
|
|
15
|
+
|
|
16
|
+
Minimal Python contracts for the Weaver Stack.
|
|
17
|
+
|
|
18
|
+
This package provides dataclasses and type definitions for all Core Weaver contracts. It has **no runtime dependencies** beyond the Python standard library (Python 3.9+).
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install weaver_contracts
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
For development (includes `pytest` and `jsonschema`):
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install "weaver_contracts[dev]"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from weaver_contracts import (
|
|
40
|
+
SelectableItem,
|
|
41
|
+
ChoiceCard,
|
|
42
|
+
RoutingDecision,
|
|
43
|
+
Capability,
|
|
44
|
+
CapabilityToken,
|
|
45
|
+
PolicyDecision,
|
|
46
|
+
Frame,
|
|
47
|
+
Handle,
|
|
48
|
+
TraceEvent,
|
|
49
|
+
)
|
|
50
|
+
from weaver_contracts.version import CONTRACT_VERSION, is_compatible
|
|
51
|
+
from datetime import datetime, timezone
|
|
52
|
+
|
|
53
|
+
# Routing
|
|
54
|
+
item = SelectableItem(id="search-1", label="Search docs", description="Search documentation")
|
|
55
|
+
card = ChoiceCard(id="card-1", items=[item], context_hint="Select a retrieval action")
|
|
56
|
+
rd = RoutingDecision(
|
|
57
|
+
id="rd-abc",
|
|
58
|
+
choice_cards=[card],
|
|
59
|
+
timestamp=datetime.now(timezone.utc),
|
|
60
|
+
selected_item_id="search-1",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Authorization
|
|
64
|
+
token = CapabilityToken(
|
|
65
|
+
token_id="tok-xyz",
|
|
66
|
+
principal="my-agent",
|
|
67
|
+
scope=["org.myapp.search_docs"],
|
|
68
|
+
issued_at=datetime.now(timezone.utc),
|
|
69
|
+
expires_at=datetime(2099, 1, 1, tzinfo=timezone.utc),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Frame (safe output)
|
|
73
|
+
frame = Frame(
|
|
74
|
+
frame_id="frame-001",
|
|
75
|
+
capability_id="org.myapp.search_docs",
|
|
76
|
+
summary="Found 3 documents matching 'query'.",
|
|
77
|
+
created_at=datetime.now(timezone.utc),
|
|
78
|
+
handle_refs=["handle-abc"], # reference to raw artifact
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Contract Tiers
|
|
85
|
+
|
|
86
|
+
| Module | Contents | Stability |
|
|
87
|
+
| -------- | ---------- | ----------- |
|
|
88
|
+
| `weaver_contracts.core` | Core contracts (all 9 types) | Stable within major version |
|
|
89
|
+
| `weaver_contracts.extended` | Optional metadata types | May evolve in minor versions |
|
|
90
|
+
| `weaver_contracts.version` | Version constants + `is_compatible()` | Stable |
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Running Tests
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
cd contracts/python
|
|
98
|
+
pip install -e ".[dev]"
|
|
99
|
+
pytest
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Schema Alignment
|
|
105
|
+
|
|
106
|
+
The JSON Schemas in `contracts/json/` are the language-agnostic source of truth. The Python types mirror the schemas exactly. If a schema changes, the corresponding Python type and tests should be updated in the same PR.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# weaver_contracts Python Package
|
|
2
|
+
|
|
3
|
+
Minimal Python contracts for the Weaver Stack.
|
|
4
|
+
|
|
5
|
+
This package provides dataclasses and type definitions for all Core Weaver contracts. It has **no runtime dependencies** beyond the Python standard library (Python 3.9+).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install weaver_contracts
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
For development (includes `pytest` and `jsonschema`):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install "weaver_contracts[dev]"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from weaver_contracts import (
|
|
27
|
+
SelectableItem,
|
|
28
|
+
ChoiceCard,
|
|
29
|
+
RoutingDecision,
|
|
30
|
+
Capability,
|
|
31
|
+
CapabilityToken,
|
|
32
|
+
PolicyDecision,
|
|
33
|
+
Frame,
|
|
34
|
+
Handle,
|
|
35
|
+
TraceEvent,
|
|
36
|
+
)
|
|
37
|
+
from weaver_contracts.version import CONTRACT_VERSION, is_compatible
|
|
38
|
+
from datetime import datetime, timezone
|
|
39
|
+
|
|
40
|
+
# Routing
|
|
41
|
+
item = SelectableItem(id="search-1", label="Search docs", description="Search documentation")
|
|
42
|
+
card = ChoiceCard(id="card-1", items=[item], context_hint="Select a retrieval action")
|
|
43
|
+
rd = RoutingDecision(
|
|
44
|
+
id="rd-abc",
|
|
45
|
+
choice_cards=[card],
|
|
46
|
+
timestamp=datetime.now(timezone.utc),
|
|
47
|
+
selected_item_id="search-1",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Authorization
|
|
51
|
+
token = CapabilityToken(
|
|
52
|
+
token_id="tok-xyz",
|
|
53
|
+
principal="my-agent",
|
|
54
|
+
scope=["org.myapp.search_docs"],
|
|
55
|
+
issued_at=datetime.now(timezone.utc),
|
|
56
|
+
expires_at=datetime(2099, 1, 1, tzinfo=timezone.utc),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Frame (safe output)
|
|
60
|
+
frame = Frame(
|
|
61
|
+
frame_id="frame-001",
|
|
62
|
+
capability_id="org.myapp.search_docs",
|
|
63
|
+
summary="Found 3 documents matching 'query'.",
|
|
64
|
+
created_at=datetime.now(timezone.utc),
|
|
65
|
+
handle_refs=["handle-abc"], # reference to raw artifact
|
|
66
|
+
)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Contract Tiers
|
|
72
|
+
|
|
73
|
+
| Module | Contents | Stability |
|
|
74
|
+
| -------- | ---------- | ----------- |
|
|
75
|
+
| `weaver_contracts.core` | Core contracts (all 9 types) | Stable within major version |
|
|
76
|
+
| `weaver_contracts.extended` | Optional metadata types | May evolve in minor versions |
|
|
77
|
+
| `weaver_contracts.version` | Version constants + `is_compatible()` | Stable |
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Running Tests
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
cd contracts/python
|
|
85
|
+
pip install -e ".[dev]"
|
|
86
|
+
pytest
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Schema Alignment
|
|
92
|
+
|
|
93
|
+
The JSON Schemas in `contracts/json/` are the language-agnostic source of truth. The Python types mirror the schemas exactly. If a schema changes, the corresponding Python type and tests should be updated in the same PR.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "weaver_contracts"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Minimal Python contracts for the Weaver Stack (contextweaver, agent-kernel, ChainWeaver)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "Apache-2.0" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
dependencies = []
|
|
13
|
+
|
|
14
|
+
[project.optional-dependencies]
|
|
15
|
+
dev = [
|
|
16
|
+
"pytest>=7.4",
|
|
17
|
+
"jsonschema>=4.17",
|
|
18
|
+
"mypy>=1.0",
|
|
19
|
+
"pytest-cov>=4.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.packages.find]
|
|
23
|
+
where = ["src"]
|
|
24
|
+
|
|
25
|
+
[tool.mypy]
|
|
26
|
+
strict = true
|
|
27
|
+
warn_return_any = true
|
|
28
|
+
warn_unused_configs = true
|
|
29
|
+
|
|
30
|
+
[tool.pytest.ini_options]
|
|
31
|
+
testpaths = ["tests"]
|
|
32
|
+
|
|
33
|
+
[tool.coverage.run]
|
|
34
|
+
source = ["weaver_contracts"]
|
|
35
|
+
|
|
36
|
+
[tool.coverage.report]
|
|
37
|
+
# Raised to 80% when Extended contract tests landed (#17, PR #34).
|
|
38
|
+
fail_under = 80
|
|
39
|
+
show_missing = true
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
weaver_contracts — Minimal Python contracts for the Weaver Stack.
|
|
3
|
+
|
|
4
|
+
Core contracts: SelectableItem, ChoiceCard, RoutingDecision,
|
|
5
|
+
Capability, CapabilityToken, PolicyDecision,
|
|
6
|
+
Frame, Handle, TraceEvent
|
|
7
|
+
|
|
8
|
+
Extended contracts: see weaver_contracts.extended
|
|
9
|
+
|
|
10
|
+
Version info: see weaver_contracts.version
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .core import (
|
|
14
|
+
SelectableItem,
|
|
15
|
+
ChoiceCard,
|
|
16
|
+
RoutingDecision,
|
|
17
|
+
Capability,
|
|
18
|
+
CapabilityToken,
|
|
19
|
+
PolicyDecision,
|
|
20
|
+
Frame,
|
|
21
|
+
Handle,
|
|
22
|
+
TraceEvent,
|
|
23
|
+
)
|
|
24
|
+
from .version import CONTRACT_VERSION, SCHEMA_VERSION_PREFIX
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"SelectableItem",
|
|
28
|
+
"ChoiceCard",
|
|
29
|
+
"RoutingDecision",
|
|
30
|
+
"Capability",
|
|
31
|
+
"CapabilityToken",
|
|
32
|
+
"PolicyDecision",
|
|
33
|
+
"Frame",
|
|
34
|
+
"Handle",
|
|
35
|
+
"TraceEvent",
|
|
36
|
+
"CONTRACT_VERSION",
|
|
37
|
+
"SCHEMA_VERSION_PREFIX",
|
|
38
|
+
]
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core Weaver contracts.
|
|
3
|
+
|
|
4
|
+
All types in this module correspond 1:1 to the JSON Schemas in contracts/json/.
|
|
5
|
+
These are minimal, stable types. No third-party runtime dependencies.
|
|
6
|
+
|
|
7
|
+
Design rules:
|
|
8
|
+
- Use dataclasses with field-level type annotations.
|
|
9
|
+
- Optional fields default to None or empty collections.
|
|
10
|
+
- Post-init validation enforces non-negotiable invariants.
|
|
11
|
+
- All IDs and required string fields must be non-empty.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
from typing import Any, Dict, List, Optional
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# SelectableItem
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class SelectableItem:
|
|
27
|
+
"""A single option within a ChoiceCard presented to the LLM for selection."""
|
|
28
|
+
|
|
29
|
+
id: str
|
|
30
|
+
label: str
|
|
31
|
+
description: str
|
|
32
|
+
capability_id: Optional[str] = None
|
|
33
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
34
|
+
|
|
35
|
+
def __post_init__(self) -> None:
|
|
36
|
+
if not self.id:
|
|
37
|
+
raise ValueError("SelectableItem.id must be non-empty")
|
|
38
|
+
if not self.label:
|
|
39
|
+
raise ValueError("SelectableItem.label must be non-empty")
|
|
40
|
+
if not self.description:
|
|
41
|
+
raise ValueError("SelectableItem.description must be non-empty")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
# ChoiceCard
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class ChoiceCard:
|
|
50
|
+
"""A bounded set of SelectableItems presented to the LLM as a structured menu."""
|
|
51
|
+
|
|
52
|
+
id: str
|
|
53
|
+
items: List[SelectableItem]
|
|
54
|
+
context_hint: Optional[str] = None
|
|
55
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
56
|
+
|
|
57
|
+
def __post_init__(self) -> None:
|
|
58
|
+
if not self.id:
|
|
59
|
+
raise ValueError("ChoiceCard.id must be non-empty")
|
|
60
|
+
if not self.items:
|
|
61
|
+
raise ValueError("ChoiceCard.items must contain at least one SelectableItem")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# RoutingDecision
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class RoutingDecision:
|
|
70
|
+
"""The output of the contextweaver routing phase."""
|
|
71
|
+
|
|
72
|
+
id: str
|
|
73
|
+
choice_cards: List[ChoiceCard]
|
|
74
|
+
timestamp: datetime
|
|
75
|
+
selected_item_id: Optional[str] = None
|
|
76
|
+
selected_card_id: Optional[str] = None
|
|
77
|
+
context_summary: Optional[str] = None
|
|
78
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
79
|
+
|
|
80
|
+
def __post_init__(self) -> None:
|
|
81
|
+
if not self.id:
|
|
82
|
+
raise ValueError("RoutingDecision.id must be non-empty")
|
|
83
|
+
if not self.choice_cards:
|
|
84
|
+
raise ValueError("RoutingDecision.choice_cards must contain at least one ChoiceCard")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
# Capability
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class Capability:
|
|
93
|
+
"""A named, versioned unit of executable functionality in agent-kernel."""
|
|
94
|
+
|
|
95
|
+
id: str
|
|
96
|
+
name: str
|
|
97
|
+
version: str
|
|
98
|
+
description: str
|
|
99
|
+
input_schema_ref: Optional[str] = None
|
|
100
|
+
output_schema_ref: Optional[str] = None
|
|
101
|
+
tags: List[str] = field(default_factory=list)
|
|
102
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
103
|
+
|
|
104
|
+
def __post_init__(self) -> None:
|
|
105
|
+
if not self.id:
|
|
106
|
+
raise ValueError("Capability.id must be non-empty")
|
|
107
|
+
if not self.name:
|
|
108
|
+
raise ValueError("Capability.name must be non-empty")
|
|
109
|
+
if not self.version:
|
|
110
|
+
raise ValueError("Capability.version must be non-empty")
|
|
111
|
+
if not self.description:
|
|
112
|
+
raise ValueError("Capability.description must be non-empty")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
# CapabilityToken
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
@dataclass
|
|
120
|
+
class CapabilityToken:
|
|
121
|
+
"""A scoped authorization credential for capability invocation."""
|
|
122
|
+
|
|
123
|
+
token_id: str
|
|
124
|
+
principal: str
|
|
125
|
+
scope: List[str]
|
|
126
|
+
issued_at: datetime
|
|
127
|
+
expires_at: Optional[datetime] = None
|
|
128
|
+
single_use: bool = False
|
|
129
|
+
issuer: Optional[str] = None
|
|
130
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
131
|
+
|
|
132
|
+
def __post_init__(self) -> None:
|
|
133
|
+
if not self.token_id:
|
|
134
|
+
raise ValueError("CapabilityToken.token_id must be non-empty")
|
|
135
|
+
if not self.principal:
|
|
136
|
+
raise ValueError("CapabilityToken.principal must be non-empty")
|
|
137
|
+
if not self.scope:
|
|
138
|
+
raise ValueError("CapabilityToken.scope must contain at least one capability ID")
|
|
139
|
+
if not self.single_use and self.expires_at is None:
|
|
140
|
+
raise ValueError(
|
|
141
|
+
"CapabilityToken must have expires_at unless single_use is True"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ---------------------------------------------------------------------------
|
|
146
|
+
# PolicyDecision
|
|
147
|
+
# ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
@dataclass
|
|
150
|
+
class PolicyDecision:
|
|
151
|
+
"""The authorization verdict produced by the agent-kernel policy engine."""
|
|
152
|
+
|
|
153
|
+
decision_id: str
|
|
154
|
+
decision: str # "allow" | "deny"
|
|
155
|
+
capability_id: str
|
|
156
|
+
principal: str
|
|
157
|
+
timestamp: datetime
|
|
158
|
+
token_id: Optional[str] = None
|
|
159
|
+
reason: Optional[str] = None
|
|
160
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
161
|
+
|
|
162
|
+
_VALID_DECISIONS = frozenset({"allow", "deny"})
|
|
163
|
+
|
|
164
|
+
def __post_init__(self) -> None:
|
|
165
|
+
if not self.decision_id:
|
|
166
|
+
raise ValueError("PolicyDecision.decision_id must be non-empty")
|
|
167
|
+
if self.decision not in self._VALID_DECISIONS:
|
|
168
|
+
raise ValueError(f"PolicyDecision.decision must be one of {self._VALID_DECISIONS}")
|
|
169
|
+
if not self.capability_id:
|
|
170
|
+
raise ValueError("PolicyDecision.capability_id must be non-empty")
|
|
171
|
+
if not self.principal:
|
|
172
|
+
raise ValueError("PolicyDecision.principal must be non-empty")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# ---------------------------------------------------------------------------
|
|
176
|
+
# Frame
|
|
177
|
+
# ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
@dataclass
|
|
180
|
+
class Frame:
|
|
181
|
+
"""A safe, filtered view of a tool execution result produced by the firewall.
|
|
182
|
+
|
|
183
|
+
Invariant: A Frame never contains raw tool output. Raw output is stored
|
|
184
|
+
as a Handle. contextweaver and the LLM consume only Frames.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
frame_id: str
|
|
188
|
+
capability_id: str
|
|
189
|
+
summary: str
|
|
190
|
+
created_at: datetime
|
|
191
|
+
structured_data: Optional[Dict[str, Any]] = None
|
|
192
|
+
handle_refs: List[str] = field(default_factory=list)
|
|
193
|
+
redaction_notes: Optional[str] = None
|
|
194
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
195
|
+
|
|
196
|
+
def __post_init__(self) -> None:
|
|
197
|
+
if not self.frame_id:
|
|
198
|
+
raise ValueError("Frame.frame_id must be non-empty")
|
|
199
|
+
if not self.capability_id:
|
|
200
|
+
raise ValueError("Frame.capability_id must be non-empty")
|
|
201
|
+
if not self.summary:
|
|
202
|
+
raise ValueError("Frame.summary must be non-empty")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# ---------------------------------------------------------------------------
|
|
206
|
+
# Handle
|
|
207
|
+
# ---------------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
@dataclass
|
|
210
|
+
class Handle:
|
|
211
|
+
"""An opaque reference to a raw artifact stored in the HandleStore.
|
|
212
|
+
|
|
213
|
+
Resolution requires authorization through agent-kernel.
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
handle_id: str
|
|
217
|
+
capability_id: str
|
|
218
|
+
artifact_type: str
|
|
219
|
+
created_at: datetime
|
|
220
|
+
expires_at: Optional[datetime] = None
|
|
221
|
+
access_policy: Optional[str] = None
|
|
222
|
+
byte_size: Optional[int] = None
|
|
223
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
224
|
+
|
|
225
|
+
def __post_init__(self) -> None:
|
|
226
|
+
if not self.handle_id:
|
|
227
|
+
raise ValueError("Handle.handle_id must be non-empty")
|
|
228
|
+
if not self.capability_id:
|
|
229
|
+
raise ValueError("Handle.capability_id must be non-empty")
|
|
230
|
+
if not self.artifact_type:
|
|
231
|
+
raise ValueError("Handle.artifact_type must be non-empty")
|
|
232
|
+
if self.byte_size is not None and self.byte_size < 0:
|
|
233
|
+
raise ValueError("Handle.byte_size must be non-negative")
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
# ---------------------------------------------------------------------------
|
|
237
|
+
# TraceEvent
|
|
238
|
+
# ---------------------------------------------------------------------------
|
|
239
|
+
|
|
240
|
+
TRACE_EVENT_TYPES = frozenset({
|
|
241
|
+
"capability_authorized",
|
|
242
|
+
"capability_denied",
|
|
243
|
+
"capability_executed",
|
|
244
|
+
"firewall_applied",
|
|
245
|
+
"handle_created",
|
|
246
|
+
"handle_resolved",
|
|
247
|
+
"token_issued",
|
|
248
|
+
"token_invalidated",
|
|
249
|
+
"flow_started",
|
|
250
|
+
"flow_step_started",
|
|
251
|
+
"flow_step_completed",
|
|
252
|
+
"flow_completed",
|
|
253
|
+
"flow_failed",
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
TRACE_EVENT_OUTCOMES = frozenset({"success", "failure", "partial"})
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@dataclass
|
|
260
|
+
class TraceEvent:
|
|
261
|
+
"""An immutable audit log entry. Append-only; must not be modified after creation."""
|
|
262
|
+
|
|
263
|
+
event_id: str
|
|
264
|
+
event_type: str
|
|
265
|
+
timestamp: datetime
|
|
266
|
+
capability_id: Optional[str] = None
|
|
267
|
+
principal: Optional[str] = None
|
|
268
|
+
decision_id: Optional[str] = None
|
|
269
|
+
frame_id: Optional[str] = None
|
|
270
|
+
handle_id: Optional[str] = None
|
|
271
|
+
outcome: Optional[str] = None
|
|
272
|
+
error_message: Optional[str] = None
|
|
273
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
274
|
+
|
|
275
|
+
def __post_init__(self) -> None:
|
|
276
|
+
if not self.event_id:
|
|
277
|
+
raise ValueError("TraceEvent.event_id must be non-empty")
|
|
278
|
+
if not self.event_type:
|
|
279
|
+
raise ValueError("TraceEvent.event_type must be non-empty")
|
|
280
|
+
if self.event_type not in TRACE_EVENT_TYPES:
|
|
281
|
+
raise ValueError(
|
|
282
|
+
f"TraceEvent.event_type must be one of {TRACE_EVENT_TYPES}"
|
|
283
|
+
)
|
|
284
|
+
if self.outcome is not None and self.outcome not in TRACE_EVENT_OUTCOMES:
|
|
285
|
+
raise ValueError(
|
|
286
|
+
f"TraceEvent.outcome must be one of {TRACE_EVENT_OUTCOMES} or None"
|
|
287
|
+
)
|