Write plugin versions while loading configs

The staging copy made by Config.set() went through the constructor, so
plugin migrations ran a second time on the live fields. A version-gated
migration was applied twice when setPluginVersions() changed the version.
Staging now copies the fields with update() instead.

A config saved without <plugin>.version got the running version as its
default, so setPluginVersions() saw no change and nothing saved the stamp.
The constructor now writes the registered plugin versions after the
migrations run and marks the config dirty when that changes a loaded value.
Controller.bootstrap() saves the instances that came out dirty, and
setPluginVersions() is gone.

Restructure the SyncHook and plugin config tests by method, and suggest the
controller config version for role migrations in the docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bbassieandClaude Opus 5.5 committed 2026-10-14 16:37:03 +00:00
1 parent aec0fc7a1e
commit e455d17bb7
10 files changed
+188 -119

No files matched your search

+3 -10
View File
@@ -148,15 +148,7 @@ export default class Controller {
}
});
/**
* Load the controller datastores
*
* @param config - Controller config.
* @param pluginInfos -
* Plugins whose migrations ran, their versions are saved to the
* instance configs.
*/
static async bootstrap(config: lib.ControllerConfig, pluginInfos: lib.PluginNodeEnvInfo[] = []) {
static async bootstrap(config: lib.ControllerConfig) {
let databaseDirectory = config.get("controller.database_directory");
await fs.mkdir(databaseDirectory, { recursive: true });
@@ -183,8 +175,9 @@ export default class Controller {
InstanceRecord.fromJSON.bind(InstanceRecord),
this.migrateInstances, this.finaliseInstances,
).bootstrap());
// Save plugin versions written while loading
for (const instance of instances.values()) {
if (lib.setPluginVersions(instance.config, pluginInfos)) {
if (instance.config.dirty) {
instances.set(instance);
}
}