v4.0.1
A follow-up to 4.0.0 covering the three items deferred out of the post-release review. No breaking changes.
Tool effects are declared, never inferred
Effects drive authorization — the inspector and builder profiles, and capability allowlists — but they were guessed from the tool's name:
const EXTERNAL_TOOL = /(asset|marketplace|image_generate|pollinations|wally|publish|import_rbxm|export_rbxm)/;That was wrong in both directions. It over-declared: export_rbxm asks Studio for bytes and writes them to disk, yet was marked as reaching the network while its actual local write went undeclared. And it under-declared, which is the dangerous half: a new tool that genuinely reaches the network inherits no network.external unless its name happens to match, and a capability policy would wave it through.
ToolDefinition.effects is now required, so an omission is a compile error rather than a silent guess. All 209 tools declare their own. They were seeded from the previously computed values so nothing could quietly lose an effect; exactly three changed, each verified by reading the handler:
| tool | before | after |
|---|---|---|
export_rbxm |
studio.read, network.external |
studio.read, local.files.write |
get_asset_provenance |
studio.read, network.external |
— |
import_rbxm |
studio.write, network.external |
studio.write, local.files.read, network.external |
Syncback recovery snapshot honours the project's ignore paths
The native syncback rollback snapshot now skips paths the project declares through globIgnorePaths and syncbackRules.ignorePaths, and both lists are part of the plan hash so changing them invalidates a preview. Rojo evaluates them per path relative to the project directory and refuses to write to a match, so those files cannot need restoring.
The snapshot is deliberately not narrowed to the dry run's reported paths: rojo syncback --list is human-readable output rather than a machine contract, and any path a parse missed would be unrecoverable after a partial failure. The 5000-file / 100 MiB ceiling stays, and its message now names both ignore lists as the way out.
ESLint 10
eslint and @eslint/js move to 10 together; typescript-eslint 8.65 already declares ESLint 10 support. Two rules new to recommended were fixed rather than switched off:
preserve-caught-error— 17 rethrows discarded the original failure when wrapping it. They now carry{ cause }, so anerrnoor a timeout stays readable underneath the BloxForge-level message.no-useless-assignment— 8 initializers whose value no code path could observe.
Full detail in CHANGELOG.md.