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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ )