Skip to content

feat: add to_sealed_dict / from_sealed_dict, fix FastAPI seal omission#16

Merged
bradleygauthier merged 1 commit intomainfrom
feat/sealed-dict-serialization
Mar 18, 2026
Merged

feat: add to_sealed_dict / from_sealed_dict, fix FastAPI seal omission#16
bradleygauthier merged 1 commit intomainfrom
feat/sealed-dict-serialization

Conversation

@bradleygauthier
Copy link
Contributor

Summary

  • Add Capsule.to_sealed_dict() — serializes both canonical content and the cryptographic seal envelope (hash, signature, signature_pq, signed_at, signed_by). to_dict() continues to return only the canonical content used for hashing.
  • Add Capsule.from_sealed_dict() — inverse of to_sealed_dict(), restores a complete sealed record from a dict. Enables full roundtrip: seal → to_sealed_dict → from_sealed_dict → verify.
  • Fix FastAPI integration endpoints (GET /capsules/ and GET /capsules/{id}) to use to_sealed_dict() so API responses include the seal envelope instead of silently dropping it.

Context

to_dict() intentionally excludes seal fields because it produces the canonical representation used for SHA3-256 hash computation — including the hash in the content being hashed would create a circular dependency. However, there was no companion method to serialize the complete sealed record, forcing every API consumer to manually extract seal attributes via getattr().

The FastAPI integration was also using to_dict() for responses, meaning the endpoints mounted by mount_capsules() were serving capsule records without their cryptographic proof.

Changes

File Change
capsule.py Add to_sealed_dict(), from_sealed_dict(), update docstrings on to_dict()/from_dict()
integrations/fastapi.py Switch list + get endpoints from to_dict() to to_sealed_dict()
test_capsule.py 13 new tests (happy path, edge cases, JSON serialization, non-mutation, key delta, partial fields)
test_seal.py 5 new tests (real Seal integration, hash stability, verify-after-roundtrip)
test_fastapi.py 2 new tests (seal fields present in list and get responses)
test_invariants.py 1 new test (to_sealed_dict superset of to_dict)
api.md Document both new methods with examples and verification comments
high-level-api.md Update FastAPI section with seal field detail and response example
CHANGELOG.md v1.5.2 entry
Version files Bump to 1.5.2 (pyproject.toml, __init__.py, version tests)

Test plan

  • 780 passed, 7 skipped, 0 failed (full suite)
  • to_sealed_dict() after real Seal.seal() returns valid crypto fields
  • compute_hash(capsule.to_dict()) still matches capsule.hash (seal isolation intact)
  • seal → to_sealed_dict → from_sealed_dict → verify roundtrip passes
  • to_dict() still excludes seal fields (existing invariant test intact)
  • FastAPI list and get endpoints include hash, signature, signature_pq, signed_at, signed_by
  • from_sealed_dict tolerates missing seal keys, partial seal keys, and plain to_dict() output
  • to_sealed_dict() output is JSON-serializable
  • Version alignment: pyproject.toml, __init__.py, spec/VERSION, CHANGELOG, test assertions all at 1.5.2

Closes #16

to_dict() intentionally excludes seal fields (hash, signature, signed_at,
signed_by) because it produces the canonical content used for SHA3-256
hashing. This left API consumers with no way to serialize a complete
sealed record without manually extracting attributes via getattr().

Add to_sealed_dict() which returns everything from to_dict() plus the
five seal envelope fields. Add from_sealed_dict() as the inverse for
roundtrip deserialization. Fix FastAPI integration endpoints (list and
get) to use to_sealed_dict() so API responses include the seal.

Closes #16
@bradleygauthier bradleygauthier merged commit a8fb0b2 into main Mar 18, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant