Skip to content

fix: structural ref resolution for record-using interfaces#21

Merged
colinrozzi merged 1 commit into
mainfrom
fix-record-hash-convergence
May 21, 2026
Merged

fix: structural ref resolution for record-using interfaces#21
colinrozzi merged 1 commit into
mainfrom
fix-record-hash-convergence

Conversation

@colinrozzi
Copy link
Copy Markdown
Owner

Summary

Handlers that declare records inside an interface name { ... } block (e.g. theater-handler-podman) could not agree on an interface hash with their actors. Two root causes:

  1. Handler side hashed Type::Ref(path) nominally (sha256("ref:" + path_str)), while the actor side hashed records structurally. Even when the actor-side type defs were in scope, the two sides produced different bytes.
  2. Actor-side parser rejected records inside imports { iface { ... } } blocks, so actors couldn't declare matching type defs at all — the only escape hatch was placing records at top level of the .types file, which still mismatched (see point 1).

This unblocks agentry-actor and any future actor calling a record-using handler.

Changes

  • src/metadata.rs: new hash_type_in(ty, types) resolves Type::Ref against in-scope typedefs and hashes the underlying record/variant structurally. Cycle-safe via a name stack returning HASH_SELF_REF. hash_type becomes a thin wrapper over hash_type_in(ty, &[]). compute_interface_hash threads arena.types through.
  • src/interface_impl.rs: removed the duplicate ref-hashing path in type_to_hash (it shadowed the metadata.rs logic and would have diverged again). FuncSignature now has hash_in(types); the old hash() delegates with no types in scope. InterfaceImpl carries a types field, and from_pact captures pact.types (plus type-style exports). This is the path theater actually uses.
  • crates/pack-guest-macros/src/lib.rs: parse_func_sigs_into accepts record/variant/enum/flags/type declarations at the top of an interface block. Declarations are scoped locally so sibling interfaces don't share resolution.
  • crates/pack-guest-macros/src/metadata.rs: added an explanatory comment about the result<_, E> → Bool/String convention shared with pact.rs::parse_result.

Tests

  • 4 new handler-side unit tests in src/metadata.rs: ref resolves structurally, unresolved ref falls back to path hash, recursive types terminate cleanly, compute_interface_hash matches a hand-computed structural hash.
  • 4 new actor-side unit tests in pack-guest-macros: parser accepts records inside imports/interface blocks, scopes don't leak between sibling interfaces, top-level typedefs reach into blocks, result<_, E> convention.
  • 1 cross-crate convergence test in tests/handler_actor_hash_convergence.rs using the actual podman.pact shape: parse_pact + InterfaceImpl::from_pact produces a byte-identical hash to packr-abi primitives applied directly. This is the test that would have failed before the fix and catches future regressions.

Test plan

  • cargo test --workspace — all 252 tests pass
  • Convergence test (handler_actor_hash_convergence) explicitly mirrors the user-reported theater-handler-podman scenario

🤖 Generated with Claude Code

Records inside an interface block were previously hashed by path string on
the handler side and rejected outright by the actor-side pack_types!
parser, so handlers using records (e.g. theater-handler-podman) couldn't
agree on an interface hash with their actors.

- src/metadata.rs: hash_type_in resolves Type::Ref against in-scope
  typedefs and hashes the underlying record/variant structurally; cycle-
  safe via a name stack returning HASH_SELF_REF.
- src/interface_impl.rs: removed the duplicate ref-hashing path; FuncSignature
  delegates to metadata::hash_type_in. InterfaceImpl::from_pact now captures
  pact.types so refs in function signatures resolve.
- crates/pack-guest-macros/src/lib.rs: parse_func_sigs_into accepts record
  /variant/enum/flags/type-alias decls inside an interface block, scoped
  locally so sibling interfaces don't share resolution.
- tests/handler_actor_hash_convergence.rs: cross-crate convergence test
  using the podman.pact shape — parse_pact + InterfaceImpl::from_pact
  produces byte-identical hash to direct packr-abi primitives.
@colinrozzi colinrozzi force-pushed the fix-record-hash-convergence branch from 5ea3855 to f68ec37 Compare May 21, 2026 03:06
@colinrozzi colinrozzi merged commit 98b4cfa into main May 21, 2026
2 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