Skip to content

feat(runtime)!: cut guest-to-guest calling and restore the single-store model - #274

Merged
mfw78 merged 1 commit into
mainfrom
feat/cut-guest-to-guest
Aug 17, 2026
Merged

feat(runtime)!: cut guest-to-guest calling and restore the single-store model#274
mfw78 merged 1 commit into
mainfrom
feat/cut-guest-to-guest

Conversation

@mfw78

@mfw78 mfw78 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Stacked on #273. Base is chore/spdx-resolver-msrv, not main. Merge #273 first; GitHub retargets this to main when it does.

Why the feature goes

No component the toolchain builds can satisfy a provides claim.

synthesize in crates/nexum-world/src/lib.rs emits one world carrying func exports only, export init and export on-event, and never an interface instance.
enforce_provides accepted only ComponentItem::ComponentInstance.
The test fixture stated the contradiction in its own header: it "exports an interface instance, which no in-tree guest can".
Zero in-tree manifests declared provides and zero configs declared [implements], so what shipped was a complete admission-control system for a capability that could not be produced or consumed.

Neither product the runtime serves needs the edge.
A daemon module is host-triggered.
A wallet plugin is host-called and returns a verdict.
A decoder plugin feeding a risk plugin is cascading host-to-guest over a host-defined interchange type, which also keeps user consent non-transitive: a user approves a set of plugins, never a call graph among them.

Operator impact

This is a config-key, refusal-wording and metric-label contract change.

The engine.toml [implements] table is removed. EngineConfig carries deny_unknown_fields, so a stale table refuses at parse as a TOML error an operator cannot distinguish from a typo. No shim was added because no config ever declared one.

[component].provides is removed from the manifest and refuses the same way.

Six labels leave the closed error_kind set: invalid_interface_id, interface_claimed, provides_not_exported, implementer_unbound, implementer_unpinned, implementer_not_claiming. The closed-set test enforces the reduced set.

DigestPin::Operator's wording changed, and its pin test changed with it.

What got stronger

The operator artifact pin moves from an [implements] row to [[modules]].digest, so it is available for every module rather than only for a provides claimant that could not exist.

ModuleEntry.digest is validated at parse with a typed EngineConfigError::InvalidModuleDigest. The operator pin and the author's [component].digest stay independent, both verified against the exact bytes handed to the compiler, operator checked first. A mismatch names which pin failed.

A review finding added tests pinning that a matching operator digest does not satisfy require_component_digest: the author pin is still required when the operator demands one.

What was kept, and why it is written down

InterfaceId and InterfaceTrack survive, less matches_export, whose only caller was enforce_provides.

The module rustdoc now records why, so the next reader does not delete them as orphans: a plugin registry must select candidates for a slot before reading bytes, and InterfaceTrack's leading-zero rule decides whether a registry update is auto-installable or needs fresh user consent.

The records

New ADR-0022 supersedes ADR-0021 in full, since ADR-0021's whole Decision was the pair.

ADR-0022 re-grounds one component, one Store on store-scoped enforcement rather than the old imports-become-the-union claim, which is true of wac but no longer inherent to the Component Model since implements and external-id merged. ResourceLimiter is store-level and memory_growing carries no instance identity, fuel and epoch deadlines are Store methods, and LinkerInstance::func_new closures carry no caller identity. None of that is fixable by any composition mode.

It concedes that WASI Virt's attenuation is structural and holds against a malicious guest, while covering import-shaped capabilities only, and it explicitly rejects same-Store separate instantiation for the same fuel, memory and poison reason.

ADR-0017 is amended where it matters most. #233 deleted the extension-installed component path partly on the stated grounds that a service edge would replace it. Cutting that edge would have left #233 standing on nothing, so the record now re-grounds it on its independent merits, an unused parallel admission path, a second registry and the shadowing defect #204 itself reported, and says plainly that a host-calls-guest seam is expected to return for the plugin engine without re-litigating #233.

ADR-0016, ADR-0018 and ADR-0020 carry in-place marks, with two-way supersession metadata restored.

Review

Seven findings, all fixed, none rejected.

Two were the same defect: production.md claimed only [[modules]].digest holds against a compromised artifact store, inconsistent with README.md and docs/02.
ModuleEntry lost its public Deserialize and regained it through #[serde(try_from)] so digest validation still runs on the public path, pinned by a new test.
ADR-0017's wac alternative still rested on the union-of-imports ground and now points at ADR-0022's re-grounding.
ADR-0022's Consequences claimed no shim variant existed; the true reason is that no config ever declared the table.
ADR-0018 needed its own marks, and ADR-0022 needed supersedes: 0016 to restore two-way metadata.
The last added the require_component_digest tests described above.

Verification

just ci green: 778 tests, content lint, zero-leak, fmt, clippy, rustdoc, guest wasms, doctests.
./scripts/msrv-lint.sh ok.

Closes #253
Closes #254
Closes #255
Closes #256
Closes #257

AI Assistance: Claude Fable 5 and Claude Opus 5 used for the implementation, review, and this description.

@mfw78
mfw78 force-pushed the chore/spdx-resolver-msrv branch from 8e315c0 to 84ad7c5 Compare August 17, 2026 04:44
Base automatically changed from chore/spdx-resolver-msrv to main August 17, 2026 04:54
…re model

Remove [component].provides and the engine.toml [implements] table with
their verification, authorization and prepass machinery: enforce_provides,
enforce_implements, the interface claim ledger, and the six refusals they
raised. The six labels leave the pinned error_kind set, a deliberate
operator-contract change the closed-set test now enforces in reduced form.

Keep InterfaceId and InterfaceTrack for the plugin registry, less
matches_export, and record why in the module rustdoc.

Move the operator artifact pin from the [implements] row to
[[modules]].digest, verified on the exact bytes handed to the compiler
beside the author's [component].digest. DigestPin::Operator now names
[[modules]].digest in engine.toml, and its wording test changes with it.

Supersede ADR-0021 with ADR-0022, which re-grounds one-component-one-Store
on store-scoped enforcement. Amend ADR-0017, ADR-0020 and ADR-0016 in
place, and repoint the operator handbook and the packaging guide at
[[modules]].digest.

BREAKING CHANGE: a manifest carrying [component].provides or an
engine.toml carrying [implements] now refuses at parse as an unknown key.

Closes #253
Closes #254
Closes #255
Closes #256
Closes #257

AI Assistance: Claude Fable 5 used for the implementation.
@mfw78
mfw78 force-pushed the feat/cut-guest-to-guest branch from 40243f7 to 0132a85 Compare August 17, 2026 04:58
@mfw78
mfw78 merged commit e78bba0 into main Aug 17, 2026
5 checks passed
@mfw78
mfw78 deleted the feat/cut-guest-to-guest branch August 17, 2026 06:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant