Appearance
Change the guard profile
The hosted shell boots in strict. In this draft a user can switch the whole shell to balanced or relaxed; the choice applies to every window and is not remembered, so every boot starts in strict again. Loosening one applet at a time, remembered, is what D-003 asks for and is planned (Roadmap). An embedding host may choose the starting profile with createKernl({ host, guard: { profile } }), which changes limits only; the sandbox, the CSP, the frame lock and consent are fixed (What a profile cannot change).
In the shell
- Open the guard panel from the desktop bar (Guard · strict · N).
- Under Profile, pick
balancedorrelaxed. The list of mitigations updates to show the new limit for each one. The CSP does not change; it never does. - The change calls
kernl.guard.setProfile(profile). kernl's own counting layers switch to the new limits at once: the ingress guard, visibility gate and focus firewall are rebuilt from the new profile, their counters restart and the window-level messages they held are dropped. kehto's runtime firewall follows too: kernl reinstalls the new rates for every open window and sets the profile's unfocused multiplier and startup burst (D-020). Requests a hidden window holds stay held. The choice is not persisted: a reload starts instrictagain.
A quarantined applet stays closed whatever the profile: its binding was revoked and its frame removed when it was quarantined. Its window's grants panel offers Release from quarantine, which clears kernl's quarantine record for that window (kernl.guard.release(windowId)) and the panel's marker; it does not bring the frame back. To run the applet again, open it again: a new window starts with a fresh budget.
The per-applet, remembered override that D-003 describes is listed under Alpha status.
In code
A profile is a GuardConfig from createGuardConfig(profile). The three guard layers each take that config in an options object, together with an optional clock and an event sink:
ts
import {
createFocusFirewall,
createGuardConfig,
createIngressGuard,
createVisibilityGate,
type GuardEvent,
type GuardProfile,
} from '@kernlize/guard';
function guardFor(profile: GuardProfile, onEvent: (event: GuardEvent) => void) {
const config = createGuardConfig(profile);
return {
config,
ingress: createIngressGuard({ config, onEvent }),
gate: createVisibilityGate({ config, onEvent }),
firewall: createFocusFirewall({ config, onEvent }),
};
}now defaults to Date.now in all three; inject it in tests. Switching a mounted window from one profile to another means, at the guard level:
- Build the three layers from the new config. The layers hold no reference to the profile, so there is no in-place update; the old
IngressGuard,VisibilityGateandFocusFirewallare replaced. - Drain what the old gate and old ingress guard still hold (
drain(windowId)) and admit it through the new layers, so nothing queued is lost. - Call
release(windowId)on the old ingress guard, or simply drop it: quarantine lives in the guard instance and does not survive the swap. - If the shell also runs kehto's runtime firewall, hand it the new limits:
setUnfocusedMultiplierandsetBurstGuardfromconfig.firewall, and the per-applet rates, which are not in a fresh config;applyNappletRules(config, dTag)adds them, and the focus firewall does this itself on first observation.
@kernlize/core does this inside createGuardPipeline: GuardFacade.setProfile(profile) rebuilds the three layers from createGuardConfig(profile), clears what they held, re-applies the known visibility of every window and emits on profile$; GuardFacade.release(windowId) clears a quarantine record on its own, which does not bring back a frame that was already removed. createKernl follows profile$: applyRuntimeFirewallProfile sets the runtime's unfocused multiplier and startup burst, and applyRuntimeFirewallRules reinstalls the rates of every open window. Applets mounted later get the rates at mount. The core page documents that surface.
What a profile cannot change
- The Content Security Policy.
createAppletCsp(profile)returns the same policy for every profile; see why andcsp.feature, scenario "the relaxed profile does not loosen the CSP". - The sandbox, the frame lock and consent.
planMountpasses the profile only tocreateAppletCsp;sandbox="allow-scripts", the nested-frame lock and the destructive-signing gate never read it. - Whether a mitigation is enabled. Every profile enables all eleven;
describeMitigations(config)only changes thelimittext. - Grants. A profile is about rate, not authority. Grant and revoke covers authority.
What a profile changes
Exact values for every limit and every per-class rate are in Guard. In one line each: balanced raises the headline limits by 1.5× to 2× (ingressBurst 8 → 12, ingressQueue 64 → 128, quarantineThreshold 300 → 420, hiddenResourceAllowance 8 → 16, deferQueue 200 → 400, envelope caps 64/256 KiB → 128/512 KiB, unfocusedMultiplier 0.25 → 0.5) and doubles every rate; relaxed goes to 24, 192, 800, 32, 600, 192/768 KiB and 0.75, and triples every rate.
Adding a profile
Do not. Three is the number the UI was designed around, and a fourth profile would be a developer deciding how much an applet may do, which SOUL.md reserves for the user. If an applet needs a limit no profile provides, that is a reason to revisit the limits in strict, with a scenario in packages/guard/features/profiles.feature that shows the applet a competent developer could not otherwise build.