You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
-**`Capsule.to_sealed_dict()`** — Serialize a Capsule including the cryptographic seal envelope (`hash`, `signature`, `signature_pq`, `signed_at`, `signed_by`). Returns everything from `to_dict()` plus the five seal fields. Use this when building API responses or exporting complete sealed records. `to_dict()` continues to return only the canonical content (the part that gets hashed).
19
+
-**`Capsule.from_sealed_dict(data)`** — Inverse of `to_sealed_dict()`. Deserializes both canonical content and seal envelope from a single dict. Missing seal keys default to empty values, so it also accepts plain `to_dict()` output. Enables full roundtrip: `seal → to_sealed_dict → from_sealed_dict → verify`.
20
+
-**21 new tests** across 4 test files — unit tests for both methods (happy path, edge cases, JSON serialization, non-mutation, exact key delta, partial seal fields), real-Seal integration tests (hash stability, verify-after-roundtrip), FastAPI endpoint assertions (seal fields present in list and get responses), and invariant tests (to_sealed_dict superset of to_dict).
21
+
22
+
### Fixed
23
+
24
+
-**FastAPI endpoints omitting seal envelope** — `GET /capsules/` and `GET /capsules/{id}` were using `to_dict()`, which excludes seal fields by design. Responses now use `to_sealed_dict()` and include `hash`, `signature`, `signature_pq`, `signed_at`, and `signed_by` alongside the capsule content.
25
+
26
+
---
27
+
14
28
## [1.5.1] - 2026-03-17
15
29
16
30
Storage column width fix. Prevents PostgreSQL `StorageError` on every capsule write.
@@ -200,6 +214,7 @@ Initial public release of the Capsule Protocol Specification (CPS) v1.0 referenc
Returns `True` if the Capsule has a hash and Ed25519 signature.
@@ -107,10 +111,36 @@ Returns `True` if the Capsule has a hash and Ed25519 signature.
107
111
Returns `True` if the Capsule also has an ML-DSA-65 post-quantum signature.
108
112
109
113
**`to_dict() -> dict[str, Any]`**
110
-
Serialize to dictionary. Produces the canonical representation used for hashing and storage.
114
+
Serialize the canonical content of this Capsule. Returns only the content fields — the part that gets hashed. Seal envelope fields (`hash`, `signature`, `signature_pq`, `signed_at`, `signed_by`) are deliberately excluded to avoid circular dependency during hash computation. For a complete representation including the seal, use `to_sealed_dict()`.
115
+
116
+
**`to_sealed_dict() -> dict[str, Any]`**
117
+
Serialize this Capsule including the cryptographic seal envelope. Returns everything from `to_dict()` plus five additional keys: `hash`, `signature`, `signature_pq`, `signed_at` (ISO 8601 string or `null`), and `signed_by`. Use this when serializing capsules for API responses, exports, or any context where the complete sealed record is needed.
Deserialize from dictionary. Restores all 6 sections.
133
+
Deserialize from a canonical content dictionary. Restores all 6 sections. Seal envelope fields, if present in *data*, are ignored. To restore a complete sealed record, use `from_sealed_dict()`.
Deserialize from a sealed dictionary. Restores both the canonical content and the seal envelope fields (`hash`, `signature`, `signature_pq`, `signed_at`, `signed_by`). This is the inverse of `to_sealed_dict()`. Missing seal keys default to empty values.
137
+
138
+
```python
139
+
# Full roundtrip with seal preservation
140
+
d = capsule.to_sealed_dict()
141
+
restored = Capsule.from_sealed_dict(d)
142
+
assert seal.verify(restored) # True — signature survives the roundtrip
| GET |`{prefix}/verify`| Verify chain integrity (query: `tenant_id`) |
731
761
| GET |`{prefix}/{capsule_id}`| Get capsule by ID (404 if missing) |
732
762
763
+
All capsule endpoints serialize using `to_sealed_dict()`, so responses include both the canonical content and the cryptographic seal envelope (`hash`, `signature`, `signature_pq`, `signed_at`, `signed_by`).
764
+
733
765
FastAPI is not a hard dependency. Raises `CapsuleError` if not installed.
734
766
735
767
**Security note:** These endpoints are read-only and do not add authentication. Protect them with your application's auth middleware in production.
All capsule endpoints serialize using `capsule.to_sealed_dict()`, so responses include both the canonical content (the 6 sections) and the cryptographic seal envelope (`hash`, `signature`, `signature_pq`, `signed_at`, `signed_by`).
208
+
207
209
#### `GET {prefix}/`
208
210
209
211
List capsules with pagination and filtering.
@@ -219,7 +221,18 @@ List capsules with pagination and filtering.
219
221
220
222
```json
221
223
{
222
-
"capsules": [...],
224
+
"capsules": [
225
+
{
226
+
"id": "a1b2c3d4-...",
227
+
"type": "agent",
228
+
"trigger": { "..." : "..." },
229
+
"hash": "e21819859fce83ea...",
230
+
"signature": "db37397b068c79...",
231
+
"signature_pq": "",
232
+
"signed_at": "2026-03-18T02:52:03+00:00",
233
+
"signed_by": "qp_key_a1b2"
234
+
}
235
+
],
223
236
"total": 42,
224
237
"limit": 20,
225
238
"offset": 0
@@ -230,7 +243,7 @@ List capsules with pagination and filtering.
230
243
231
244
Get a single capsule by UUID.
232
245
233
-
Returns the full capsule dict, or 404 if not found.
246
+
Returns the full sealed capsule dict (content + seal envelope), or 404 if not found.
Copy file name to clipboardExpand all lines: reference/python/pyproject.toml
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
5
5
[project]
6
6
name = "qp-capsule"
7
-
version = "1.5.1"
7
+
version = "1.5.2"
8
8
description = "Capsule Protocol Specification (CPS) — tamper-evident audit records for AI operations. Create, seal, verify, and chain Capsules in Python."
0 commit comments