Files
solvecraft/docs/plugin-test-guide.md
raykholo 53a76b23f9 Plugins: sandboxed WebAssembly host with permissions and no network, phase 1 of the plugin RFC (#1221) (#15112)
* Add an explicit native Wasm command plugin runtime

* Document the plugin test workflow in English

* Report plugin execution causes and explicit fuel budgets

* Allow explicit user plugin resource ceilings

* Document the persistent isolated plugin test profile

* Require the Wasmtime 48.0.8 security fixes

Raise the minimum native runtime version and update its locked Cranelift and
Wasmtime dependencies for RUSTSEC-2026-0422, RUSTSEC-2026-0423 and
RUSTSEC-2026-0424. Preserve the existing plugin resource defaults and approval flow.

* claim: #1221 phase 1

* Plugins: share host contracts and reject unused network imports

Split plugin manifests, permissions, limits, typed errors, entry schemas and
the PluginHost trait into craft-plugin. Put the Wasmtime sandbox in
craft-plugin-wasm, with no dependencies on SolidCraft crates and no WASI
or network linker. SolidCraft supplies its command catalog and transactions,
and the UI routes namespaced plugin calls through the existing worker.

Inspect a component's imports before instantiating it (bounded).
Refuse sockets, HTTP and fetch imports even when Wasmtime would elide an
unused import. Keep net manifest requests forbidden and report typed errors.

Exercise WAT components against a tiny independent app and SolidCraft. Cover
file reads and writes without grants, swallowed denials, granted reads,
runaway fuel exhaustion, traps and rollback, network refusal, read-only
entries, exact approval and one-step undo/redo. Attribute the shared fixture.

Checked:
- cargo test -p craft-plugin-wasm: 11 passed
- cargo test -p solidcraft-engine --features plugins plugin: 16 passed
- cargo check -p solidcraft-ui-egui --all-targets: passed
- cargo clippy -p craft-plugin -p craft-plugin-wasm -p solidcraft-engine --all-targets --features solidcraft-engine/plugins -- -D warnings: passed
- cargo xtask layers: passed, 35 crates
- cargo xtask lists: passed
- cargo fmt -p craft-plugin -p craft-plugin-wasm -p solidcraft-engine -p solidcraft-ui-egui -p xtask -- --check: passed

Full-suite and remaining platform checks are left to CI after push.
Refs #1221, phase 1; design review remains required before phase 2.

* Plugins: model.mesh is an export in the mesh-decisions check

The plugin command hands a plugin a bounded copy of a body's display
tessellation; it decides nothing about the model. It joins the check's list
of display and export functions.

* Cargo.lock: the plugin crates follow the workspace version (0.40.1)

* Kernel cases: the plugin commands name where they are tested

model.mesh, plugin.inspect, plugin.list and plugin.run take no kernel case
(the folder takes no new ones); each EXEMPT line names the engine's plugin
test file.

* Plugins: enforce command authority, file scopes and per-run consent

Stage granted file writes until successful execution and app commit.
Check caller scopes for every file path and default command effects to edit.
Preserve inspection identity, preflight complete scripts and clean up accepted sketch exits.
Correct plugin schemas, preference drafts, fixture identifiers and runtime documentation.
Add regressions in the existing engine, runtime, UI and CLI test files.
Restore package-folder mismatch diagnostics and initialise consent-test catalogs.

* Plugin tests: keep a test-only command out of the icon audit

* Plugins: a link inside a granted folder is never followed

File authorization looks at a path as the plugin spells it, so a visible
symbolic link to a hidden folder (public -> .hidden) made
public/secret.txt readable, and let staged writes land in the hidden
folder, when no caller scope was set. Every file access now checks each
part of the requested path inside the granted folder and refuses a link
(or a Windows junction), for reads, writes and staged publication alike.
A plugin cannot create links itself: it only writes new regular files.

Test: a visible link to a hidden folder, and a file link to a hidden
file, are refused for reads and writes, nothing is staged through them,
and a plain file beside them still reads.

* Show plugin entry fuel use in CLI debug logs

* Plugin runtime README: links inside a granted folder are refused

* Plugins: main's new operations-panel tests build the Answer job with no inspection

main added two tests in control_job.rs that construct `Work::Answer` as a
unit value; on this branch the variant carries `inspection`.

* Designs keep plugin data under plugins/<plugin-id>/ untouched

A design container reserves plugins/<plugin-id>/ for a plugin's own files
(plugin_data, base64 by <plugin-id>/<path>, in a plain JSON design). They are
kept and saved back byte for byte whether or not the plugin is installed, and
opening a design never needs a plugin. One pending format file; the io design
tests cover save/open with the namespace and without the plugin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Plugins 0.1.0: craft:plugin@0.1.0, manifest capabilities, every MCP command

- The WIT package is craft:plugin@0.1.0; today's surface is the commands@0.1
  capability (engine -> commands, exported command -> entry). ui, viewport,
  formats and storage arrive later as their own versioned interfaces; 0.x may
  break between minors.
- plugin.json lists "capabilities": ["commands@0.1"] instead of runtime.api.
  The host refuses an unknown or newer capability at load with a typed,
  plain message ("needs commands 0.2; update SolidCraft or the plugin"), and a
  component importing another interface version before instantiation.
- The hard-coded command allowlist is gone: every command in the MCP catalog
  can be listed. Sensitive groups (file, doc, app, plugin) are refused unless
  the manifest requests them in permissions.command_groups and the user
  grants them; a granted file or doc command sees only the granted folders.
- Remove the stray REVIEW_REPORT.md; update docs and the test guide.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Plugins 0.1.0: cargo fmt

---------

Co-authored-by: kenos <crono2srv@gmail.com>
Co-authored-by: Brennen Herbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 21:18:11 +00:00

13 KiB

Command plugin test guide

This guide exercises the experimental native Wasm command host with the independent example-box plugin. The plugin creates an ordinary 20 x 10 x 3 mm box. It does not implement the reconstruction, conversion or mesh UI proposed in #8061.

What you need

Use a Windows x64 test distribution built from this branch, extracted into a writable local folder. The distribution contains the GUI, CLI, a compiled component, scripts, source, dependency notices, this manual and file checksums. It is an unsigned test build, not an official release or installer. Running its checks does not require Rust, wasm-tools, an AI account or additional network permissions.

The source checkout alone does not contain the executables or compiled component. Developers can build the native host using the instructions below. The independent guest source, package instructions and test-distribution launchers live outside this repository; they cannot be built from this checkout. A package must contain a WebAssembly component, not just a core Wasm module.

Use only a separately supplied test distribution built for this host's craft:plugin@0.1.0 WIT. Its manifests say "capabilities": ["commands@0.1"]. Kits from the earlier solidcraft:plugin@1.0.0 preview are refused at inspection with a message saying what they were built for; editing their manifest alone does not update compiled components, which must be rebuilt against the new WIT. Check the distribution's BUILD-INFO.json and file checksums before starting. This guide does not supply that distribution.

Run the Windows test distribution

  1. Extract the ZIP. In File Explorer, open the extracted folder that contains Run-Plugin-Test.cmd and Start-SolidCraft-Test.cmd.

  2. Open plugins/example-box/plugin.json in a text editor. It needs the capability commands@0.1, and its only command is solid.box; this example cannot read the existing design's mesh. Close the editor without saving changes.

  3. Return to that folder in File Explorer. Press Ctrl+L, type cmd, and press Enter. A Command Prompt opens in the extracted folder. Type the following and press Enter:

    Run-Plugin-Test.cmd
    
  4. Wait for the command to finish. Keep the Command Prompt open and look for this exact line:

    PASS: 600 mm3 / one Undo / Redo / saved model / snapshot.
    

    PASS means the automatic checks succeeded. They inspect the exact bundled component, run it, check the volume, undo once, redo, save and reopen the design, and render an image. You do not need to inspect an exit code or operate Undo/Redo yourself. At the start, Results: prints the full output-folder path. Record that path: its plugin-box.sc is the file to open in the next step.

    If you see FAILED. See ... instead, stop here and include that line and the referenced test.log when reporting the failure. To read the log, copy its folder path from Results: into File Explorer's address bar, press Enter, right-click test.log, choose Open with, and select Notepad.

  5. In the extracted folder, double-click Start-SolidCraft-Test.cmd. In the test GUI, press Ctrl+O, enter the full path to plugin-box.sc in the results folder you recorded, and open it. The box should appear as an ordinary editable native feature.

The command-line test creates a fresh results folder each time. The GUI launcher uses an isolated application data and recovery profile under results/gui-profile and places the sample in its isolated plugin folder. Neither launcher replaces your normal SolidCraft settings or installed plugins.

The test GUI retains its settings under results/gui-profile between launches. The English starter profile is copied only on first launch, so changes to plugin ceilings persist. Use one test GUI at a time with this profile. Automatic AI-app registration and update checking are off in this isolated profile.

Check Expected result
After plugin.run One body, volume 600 mm3
After one Undo No bodies
After Redo One body, volume 600 mm3
Reopen the saved file One body, volume 600 mm3; no plugin execution
Snapshot An image of the box in the same results folder

Undo/Redo in the script use one CLI session. Opening its saved design in a new GUI session does not restore the CLI's session history. You can inspect and edit the native box in the GUI. A saved result can be evaluated without the component installed.

Inspect and run another package

plugin.inspect reads its static manifest and component bytes without executing plugin code. Review the package, its command grants and limits before passing its returned approval hash to plugin.run. The hash identifies exact content; it is not a publisher signature or proof that a package is safe.

A CLI script is a JSON array. For example, save this as inspect.json, with a path relative to the directory from which you run the CLI:

[{"command":"plugin.inspect","params":{"path":"plugins/example-box"}}]

Run bin\solidcraft-cli.exe run inspect.json --results --json-out inspection.json. Copy the approval value from the first result into a separate run.json:

[{
  "command":"plugin.run",
  "params":{
    "path":"plugins/example-box",
    "approval":"COPY_THE_EXACT_APPROVAL_FROM_INSPECTION",
    "entry":"make",
    "params":{"length":20,"width":10,"height":3}
  }
}]

Then run bin\solidcraft-cli.exe run run.json --results --save box.sc. Changing even whitespace in the manifest requires inspecting and reviewing it again. A desktop control client must send plugin.run as a standalone engine.execute request; desktop batches and scripts refuse it before applying any earlier edits.

GUI behavior and current boundaries

Preferences > Plugins lists inert package metadata. Listing or enabling a package never executes it. The CLI/MCP and standalone desktop command are the execution entry points; this version has no dedicated plugin action panel or installer. Desktop plugin calls compute on a worker. Stop, a changed design/selection/context, or changed disabled-plugin preferences or resource ceilings prevent the pending result from being adopted. The CLI is a separate process and does not inherit the GUI's plugin settings.

Guest code receives no WASI filesystem, network or process imports. Its exact manifest grants are intersected with host policy. Commands run on a scratch design under an empty file scope; a refused call, trap, invalid result or exceeded budget discards the transaction, including errors caught by the guest. A successful edit is committed as one undo step.

The optional model.mesh grant returns an evaluated body's component-local vertices and triangle indices, up to 8192 vertices and 16384 triangles, subject also to a 1 MiB result cap. It contains no reconstruction or conversion algorithm. The bundled box sample does not request that grant.

The API identifier is the package declaration in the shared WIT. craft-plugin defines the app-independent manifest and PluginHost trait; craft-plugin-wasm supplies the native runtime. The SolidCraft engine supplies its command schemas and transaction implementation. Defaults are 10 million fuel, 64 MiB guest memory and a 5000 ms execution deadline. Component compilation and native CAD work are outside the guest execution budget. Browser execution, automatic updates, custom panels, persistent guest storage and plugin-defined parametric feature types are not implemented. See the runtime reference for exact limits and supported commands.

A design may carry plugin data under plugins/<plugin-id>/ in its container. Opening and saving such a design with no plugin installed keeps that data byte for byte; nothing in this version writes it yet.

Entries declare read/edit effects, input schemas and optional named menu/ribbon slots. The app can inspect these declarations with plugin.list and engine.describe. Selection and target inputs are lists. A read entry cannot run an edit command. Named files permissions require matching app-approved folders; the guest has no ambient file access. The optional clock permission enables wall time; without it the clock returns a fixed epoch. Network permission requests are refused, and components importing sockets, HTTP or fetch are refused before instantiation, even when the import is unused.

Explicitly allow a larger plugin budget

The box sample uses the unchanged invocation defaults; it needs no settings changes. Use the following steps only for a separate package whose reviewed manifest requests a larger budget. The original host ceilings are 100,000,000,000 fuel and 128 MiB. Raising either ceiling does not change the sample's request or run any plugin.

For the desktop:

  1. Inspect the package with plugin.inspect and read manifest.runtime.limits, host_limits, within_host_limits and any limits_error. This step does not execute code.
  2. Open Preferences > Plugins. Enter a positive integer in Max plugin fuel and in Max plugin memory (MiB), large enough for the specific request you reviewed. For example, a request of 200,000,000,000 fuel and 256 MiB fits ceilings of 200000000000 and 512.
  3. Choose Apply to accept both fields and keep the dialog open, or OK to accept and close it. Enter also accepts both fields. Invalid or incomplete values keep the dialog open. Cancel restores changes since the last Apply; Restore defaults resets this page.
  4. Inspect the package again to check within_host_limits: true. Review its exact manifest, code and grants, then invoke plugin.run with that inspection's approval. A host ceiling is not a substitute for approval of external code. The package still uses only its declared budget. If the manifest changes, inspect and approve the new content again.

For a standalone CLI:

  1. Open Notepad, paste the JSON below, and save it as plugin-limits.json in your extracted test package folder. In the Save dialog choose All files, so the name does not gain .txt; select UTF-8 encoding.

    {"max_fuel": 200000000000, "max_memory_mib": 512}
    
  2. Open a terminal in that folder and include --plugin-limits plugin-limits.json in each command that should use these ceilings, for example:

    bin\solidcraft-cli.exe --plugin-limits plugin-limits.json run inspect-plugin.json --results
    

    Here inspect-plugin.json is the inspection script for the package you are reviewing, following Inspect and run another package above. Keep its package path and reviewed approval hash in the run script; use the same startup flag for that run command. Missing settings keys use the original ceilings; omitting the flag uses both original ceilings.

  3. Headless MCP uses the same flag with mcp --headless. For a connected desktop, change the desktop preferences instead; the CLI refuses to override a connected application's policy.

Fuel accepts positive integers through 18446744073709551615. Memory accepts 1..4096 MiB because this runtime uses Wasm32. These are guest limits; component compilation, native CAD work and other SolidCraft memory are separate. Choose values suitable for available machine resources. The execution deadline remains independently capped at 30 seconds.

Build from source

Build the native GUI/CLI following the build guide:

cargo build --locked --profile ci -p solidcraft -p solidcraft-cli

Plugin examples and converters, including the example-box guest and its distribution scripts, live outside this repository, per the plugin RFC. This checkout supplies the host WIT and synthetic host-test fixtures; it does not supply the guest source or build instructions. Obtain an independently distributed package and follow its own instructions. Use plugin.inspect to obtain that package's actual hash before explicitly running it. Native binary output paths depend on Cargo's target configuration.

Troubleshooting

  • No result window: use Command Prompt as described in step 3. Double-clicking the test CMD may close its window before you can read it.
  • Approval mismatch: restore the bundled files, or inspect and review modified package contents before using their new hash.
  • Component compilation failed: verify this is a compatible WebAssembly component, not a core Wasm module, DLL or executable.
  • Command is not granted: the manifest must list the exact command id; there are no wildcard grants. Every command in the MCP catalog can be listed.
  • Command needs the file (or doc, app, plugin) permission: that command reaches past the open design. The manifest must request the group in runtime.permissions.command_groups, and the user must grant it. This test build has no grant UI, so such commands are refused.
  • "needs commands 0.2; update SolidCraft or the plugin": the plugin was built for a newer capability than this build has. Use a matching build. "was built for solidcraft:plugin@1.0.0" means a kit from the earlier preview: rebuild it against craft:plugin@0.1.0.
  • Desktop asks for a standalone request: send one engine.execute containing plugin.run, outside engine.batch and engine.script.
  • Check build identity: consult the distribution's BUILD-INFO.json, SHA256SUMS.txt and the checksum beside its ZIP. Keep the generated test.log with any failure report. Test distributions are unsigned and are not releases.