Plugins: real on/off toggles (instance and user), Core plugins locked #85

Closed
opened 2026-09-25 11:25:08 +00:00 by kayg · 6 comments
Owner

Owner decision (DESIGN §35, grill 2026-09-25): Settings → Plugins lists everything as 'Built in' with no toggles. Each plugin must be a real toggle.

  • Two levels: the admin toggles a plugin per instance; a user toggles modes they do not want (where the manifest allows a user toggle).
  • Off = hidden in the UI, jobs stopped, API/DAV routes return 404 (enforce_plugin_access already exists), and data is never deleted. On again = everything returns.
  • Core: Files, Notes, Calendar (and internal System, Tags) cannot be turned off; show 'Core', not 'Built in'.
  • Dependencies: turning off a plugin that another enabled plugin needs asks first and lists the dependents (e.g. Calendar → Notifications).
  • Today: PluginState has is_enabled / set_instance_enabled / set_user_enabled, but only InMemoryPluginState exists (crates/calternal-server/src/main.rs:254); nothing persists and no endpoint calls the setters. Persist it in the Index (security/state stays in the index).
Owner decision (DESIGN §35, grill 2026-09-25): Settings → Plugins lists everything as 'Built in' with no toggles. Each plugin must be a real toggle. - Two levels: the admin toggles a plugin per instance; a user toggles modes they do not want (where the manifest allows a user toggle). - Off = hidden in the UI, jobs stopped, API/DAV routes return 404 (enforce_plugin_access already exists), and data is never deleted. On again = everything returns. - Core: Files, Notes, Calendar (and internal System, Tags) cannot be turned off; show 'Core', not 'Built in'. - Dependencies: turning off a plugin that another enabled plugin needs asks first and lists the dependents (e.g. Calendar → Notifications). - Today: PluginState has is_enabled / set_instance_enabled / set_user_enabled, but only InMemoryPluginState exists (crates/calternal-server/src/main.rs:254); nothing persists and no endpoint calls the setters. Persist it in the Index (security/state stays in the index).
Author
Owner

Starting plugin-toggles on branch job/plugin-toggles from dev at c5389929cd. Initial inspection confirms the existing PluginState trait is in calternal-plugin and the Index migration runner is namespaced; I am mapping auth freshness, job claiming, and UI catalog paths before implementing persisted state and endpoints.

Starting plugin-toggles on branch job/plugin-toggles from dev at c5389929cda924e64708eec57847bfcf7c385db7. Initial inspection confirms the existing PluginState trait is in calternal-plugin and the Index migration runner is namespaced; I am mapping auth freshness, job claiming, and UI catalog paths before implementing persisted state and endpoints.
Author
Owner

Finding: the worker registers only handlers returned by registry.job_handlers(None) at startup (crates/calternal-server/src/wire.rs:1485), so later disablement cannot stop new leases. DAV is mounted separately from the Plugin router (wire.rs:453), so the existing route guard does not cover it. I am adding a live worker claim predicate and a Notes DAV access layer; active jobs keep the current Worker behavior and finish under their lease.

Finding: the worker registers only handlers returned by `registry.job_handlers(None)` at startup (crates/calternal-server/src/wire.rs:1485), so later disablement cannot stop new leases. DAV is mounted separately from the Plugin router (wire.rs:453), so the existing route guard does not cover it. I am adding a live worker claim predicate and a Notes DAV access layer; active jobs keep the current Worker behavior and finish under their lease.
Author
Owner

Implementation decision for #85: Notes stays Core and does not allow per-user toggles because Calendar requires Notes and the request defines cascade only for instance-level changes. Photos is the one current optional mode and allows a per-user toggle; Notifications, Video, AI and Search do not expose personal mode toggles. The API stores user preference state separately from instance enablement.

Persistence, dependency planning, admin/user endpoints, runtime route/search guards, DAV guard, live job claim checks, and OpenAPI/client generation are committed as 67279a6a98. Focused plugin and server tests pass; the full gates remain in progress.

Implementation decision for #85: Notes stays Core and does not allow per-user toggles because Calendar requires Notes and the request defines cascade only for instance-level changes. Photos is the one current optional mode and allows a per-user toggle; Notifications, Video, AI and Search do not expose personal mode toggles. The API stores user preference state separately from instance enablement. Persistence, dependency planning, admin/user endpoints, runtime route/search guards, DAV guard, live job claim checks, and OpenAPI/client generation are committed as 67279a6a989742ee162efcabba1b1f17b5cbfbce. Focused plugin and server tests pass; the full gates remain in progress.
Author
Owner

Validation finding for #85: The first server test run returned 404 for five existing Search API tests because their test registry used only the distributed core-plugin slice, which does not contain the separately registered Search Plugin. I changed the test helper to build the same complete registry as production. The rerun passed: 17 passed, 0 failed, 2 ignored. No production route failure was involved.

Validation finding for #85: The first server test run returned 404 for five existing Search API tests because their test registry used only the distributed core-plugin slice, which does not contain the separately registered Search Plugin. I changed the test helper to build the same complete registry as production. The rerun passed: 17 passed, 0 failed, 2 ignored. No production route failure was involved.
Author
Owner

Adversarial finding for #85: Round 1 ended with ==== FINDINGS 0. In round 2 the new Plugin probes completed, but the later deletion section attempted a second owner passkey assertion refresh and got 401 invalid_token. The shared helper re-imported an authenticator credential from before the prior assertion, so its signature counter was stale. The test now reuses a still-fresh assertion for four minutes and exports the updated virtual credential after refresh. I am rerunning the full adversarial gate.

Adversarial finding for #85: Round 1 ended with `==== FINDINGS 0`. In round 2 the new Plugin probes completed, but the later deletion section attempted a second owner passkey assertion refresh and got `401 invalid_token`. The shared helper re-imported an authenticator credential from before the prior assertion, so its signature counter was stale. The test now reuses a still-fresh assertion for four minutes and exports the updated virtual credential after refresh. I am rerunning the full adversarial gate.
Author
Owner

Finished #85 on branch job/plugin-toggles.
Head: 0330c07f4e

Gates:

  • cargo fmt --check: no stdout; exit_code=0.
  • cargo clippy --workspace --all-targets -- -D warnings: Finished dev profile [unoptimized + debuginfo] target(s) in 1m 01s; exit_code=0.
  • cargo test --workspace: exit_code=0. Affected suite output included test result: 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.15s (calternal-db) and test result: 17 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 3.00s (calternal-server).
  • bash packages/api-client/check-generated.sh: 🚀 ../../contracts/openapi.json → src/generated.ts [310.9ms]; exit_code=0.
  • bun run check in apps/web: svelte-check found 0 errors and 0 warnings.
  • bun run test in apps/web: Test Files 34 passed (34); Tests 236 passed (236).
  • bun run build in apps/web: ✓ built in 18.67s; Wrote site to "build"; ✔ done.
  • bash tests/adversarial/run.sh: ==== FINDINGS 0; ==== ROUND 2 FINDINGS 0; restart probe: 0 findings; exit_code=0.
  • cargo clean: Removed 21995 files, 15.3GiB total.

Implementation decisions: Notes stays Core and does not permit user toggles because Calendar requires Notes and the API only defines instance-level cascade. Photos is the current optional mode with user toggle enabled; Notifications, Video, AI and Search do not expose per-user toggles. Search is core: false because the request's explicit Core list names Files, Notes, Calendar, System and Tags.

The current manifest graph has no enabled non-Core dependent that can be disabled: every dependency target with dependents is Core. The server endpoint test exercises the dependent list and cascade branch with a synthetic manifest graph; the live adversarial probe verifies cascade=true cannot disable Core. The Plugins production screenshot was attached for visual review. No pushes, deploys or merges were performed.

Finished #85 on branch job/plugin-toggles. Head: 0330c07f4eb749b5cd057b11a41759469b0abb1c Gates: - `cargo fmt --check`: no stdout; exit_code=0. - `cargo clippy --workspace --all-targets -- -D warnings`: `Finished dev profile [unoptimized + debuginfo] target(s) in 1m 01s`; exit_code=0. - `cargo test --workspace`: exit_code=0. Affected suite output included `test result: 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.15s` (calternal-db) and `test result: 17 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 3.00s` (calternal-server). - `bash packages/api-client/check-generated.sh`: `🚀 ../../contracts/openapi.json → src/generated.ts [310.9ms]`; exit_code=0. - `bun run check` in apps/web: `svelte-check found 0 errors and 0 warnings`. - `bun run test` in apps/web: `Test Files 34 passed (34)`; `Tests 236 passed (236)`. - `bun run build` in apps/web: `✓ built in 18.67s`; `Wrote site to "build"`; `✔ done`. - `bash tests/adversarial/run.sh`: `==== FINDINGS 0`; `==== ROUND 2 FINDINGS 0`; `restart probe: 0 findings`; exit_code=0. - `cargo clean`: `Removed 21995 files, 15.3GiB total`. Implementation decisions: Notes stays Core and does not permit user toggles because Calendar requires Notes and the API only defines instance-level cascade. Photos is the current optional mode with user toggle enabled; Notifications, Video, AI and Search do not expose per-user toggles. Search is `core: false` because the request's explicit Core list names Files, Notes, Calendar, System and Tags. The current manifest graph has no enabled non-Core dependent that can be disabled: every dependency target with dependents is Core. The server endpoint test exercises the dependent list and cascade branch with a synthetic manifest graph; the live adversarial probe verifies `cascade=true` cannot disable Core. The Plugins production screenshot was attached for visual review. No pushes, deploys or merges were performed.
kayg closed this issue 2026-09-25 12:58:41 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#85
No description provided.