Skip to content

venue: install-time body-version handshake#448

Merged
mfw78 merged 1 commit into
dev/m1from
feat/m2-body-versions-handshake
Jul 21, 2026
Merged

venue: install-time body-version handshake#448
mfw78 merged 1 commit into
dev/m1from
feat/m2-body-versions-handshake

Conversation

@mfw78

@mfw78 mfw78 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Closes #382

What

Adds videre:venue/adapter.body-versions() -> list<u32> to the venue WIT surface and an install-time handshake: a keeper module declares the one body-schema version it encodes, a venue adapter declares the set it decodes, both via a new opaque ExtensionSections manifest carrier (#[serde(flatten)] on Manifest). Supervisor::install now runs each wired Extension's admit_provider/admit_worker predicate before compiling, refusing an undeclared manifest section or a version-mismatched pair fail-fast. videre-host implements the predicate in a new handshake.rs, asserting a keeper's version is decoded by every installed adapter.

Why

Bodies are opaque list<u8> with a guest-side borsh version tag, so schema agreement between a keeper and its adapter was never a checked property; a mismatch failed obscurely at runtime instead of at boot. This wires the handshake through the generalized Extension seam rather than freezing the WIT surface.

Closes #382

Testing

cargo nextest run --workspace --all-features: new supervisor unit tests for undeclared-section refusal, and new videre-host e2e tests (e2e_mismatched_body_versions_refuse_the_pair_at_boot, e2e_manifest_export_divergence_refuses_the_adapter_at_boot).

AI Assistance

Implemented with Claude Code.

@lgahdl lgahdl left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid, well-tested handshake — verified the core correctness questions: admit_worker always sees the complete adapter set (providers load before modules, no partial-view ordering hazard), version-set membership is a real .contains() check (not emptiness), the export-vs-manifest divergence test uses a genuine mismatch, and enforce_extension_sections refuses any unclaimed section rather than silently dropping it. Three things worth a look:

/// `[venue] body_versions` set contains that version. Every installed
/// venue is a legal runtime submit target, so one non-decoding adapter
/// refuses the keeper.
pub(crate) fn admit_worker(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither this function's doc nor admit_provider's mentions the vacuous-pass case: a keeper whose manifest omits the [venue] section entirely is admitted unconditionally, regardless of what adapters are installed — the check only fires when a keeper opts in by declaring the section. That's a reasonable default (backward-compat for non-venue keepers) but reads, from the doc alone, as if every keeper is checked. Worth stating the precondition explicitly and why silent opt-out is intentional.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed still valid at HEAD - admit_worker (handshake.rs:94) and admit_provider (:47) still carry no note on the vacuous opt-out. Tracked as a doc note in #519.

/// opaquely. The runtime ascribes them no meaning; it routes them
/// to the wired extensions' install predicates, and a section no
/// extension claims is refused at boot.
#[serde(flatten)]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#[serde(flatten)] into a BTreeMap<String, toml::Value> has no uniqueness check across wired extensions' claimed section names — enforce_extension_sections only verifies a section is claimed by at least one extension, not by at most one. If a second extension later picks the same section name (e.g. another one also wants "venue" for something unrelated), nothing detects the collision; whichever extension's parser runs first silently wins. Worth asserting uniqueness of manifest_sections() across the wired extension set at boot.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed still valid at HEAD. enforce_extension_sections (supervisor.rs:358) is still .any(|ext| ext.manifest_sections().contains(...)) - at-least-one, never at-most-one. Same collision class as the event-seam dup-kind gap, so I folded it into #517: one boot-time uniqueness pass covers subscription kinds and manifest section names, mirroring the service-namespace bail HostServices::from_extensions already does.

.await
.map_err(anyhow::Error::from)
.context("read adapter body-versions")?;
crate::handshake::verify_exported_versions(venue_id.as_str(), &declared, exported)?;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This divergence check runs after bindings.instantiate (line 701), not before compile/instantiation like admit_provider/admit_worker in supervisor.rs. That's unavoidable (you can't call a guest export before instantiating), but it means a malicious/buggy adapter component still fully instantiates — and any instantiation-time side effects run — before this specific check catches the export/manifest mismatch. Worth a doc note clarifying that only the manifest-section predicates are pre-compile; this export check is necessarily post-instantiation, pre-init.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed still valid at HEAD - the divergence check still runs after VenueAdapter::instantiate_async (registry.rs:716) with no note on the ordering. Agreed it is unavoidable (no guest export before instantiation); tracked as a doc note in #519 alongside the vacuous opt-out.

@mfw78
mfw78 force-pushed the feat/m2-videre-host branch from 1b9074a to a4e316f Compare July 21, 2026 00:52
Base automatically changed from feat/m2-videre-host to dev/m1 July 21, 2026 02:22
@mfw78
mfw78 force-pushed the feat/m2-body-versions-handshake branch from d0ec1e9 to bcccf1b Compare July 21, 2026 02:23
@mfw78
mfw78 merged commit 35a99db into dev/m1 Jul 21, 2026
5 of 6 checks passed
@mfw78
mfw78 deleted the feat/m2-body-versions-handshake branch July 21, 2026 05:16
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.

2 participants