Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/kehto-seams-core.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@kernl/core": minor
---

Adopt the host seams released in `@kehto/shell` 0.32.0, `@kehto/runtime` 0.28.0 and `@kehto/firewall` 0.7.0 (D-020).

- Port traffic is decided through `ShellAdapter.filterNappletMessage`. `GuardPipeline.observe` now returns kehto's `NappletMessageDecision` (`dispatch`, `defer` or `drop`). Breaking: `refuseCurrentDispatch` is removed, and what the guard will not let through is dropped with no reply instead of refused with a `firewall:` error.
- A hidden window's non-control requests are held and released with `ShellBridge.releaseDeferred` when it becomes visible (`GuardPipelineOptions.onRelease`), instead of refused. The `deferred-overflow` revocation removes the frame like every other reason.
- The adapter sets `aclDefaultPolicy: 'restrictive'`; `KernlGrants.ensure` grants what the record needs and revokes only what the stored entry holds beyond it.
- Breaking: `ShellAdapterDeps` replaces `onNappletMessage` with `filterNappletMessage`, `getFocusContext` and `runtimeFirewall`.
- New `applyRuntimeFirewallProfile`, `createRuntimeFirewallHooks`, `toGuardEvent` and `RUNTIME_FIREWALL_STORAGE_KEY`: the runtime firewall gets the profile's unfocused multiplier and startup burst on boot and on every profile change, open windows get the new rates, runtime refusals become guard events without the applet's message, and the runtime firewall's configuration is kept at `kernl:firewall:v1`.
- Requires `@kehto/services` ^0.30.1, `@napplet/core` >=0.38.0 <0.39.0 and `@napplet/nap` >=0.37.0 <0.38.0.
5 changes: 5 additions & 0 deletions .changeset/kehto-seams-guard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kernl/guard": patch
---

Depend on `@kehto/firewall` ^0.7.0 and accept `@napplet/core` 0.38. The `hidden-deferral` mitigation now says a hidden applet's requests are held until it is visible, and `focus-tightening` is provided by both kehto and kernl, because the runtime firewall applies the unfocused multiplier too (D-020).
5 changes: 5 additions & 0 deletions .changeset/kehto-seams-messaging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kernl/messaging": patch
---

Accept `@napplet/core` 0.38 as a peer.
8 changes: 8 additions & 0 deletions .planning/HANDOFF.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,11 @@ All three agents are done. Ship: re-run `pnpm check` and `pnpm exec playwright t
## Shipped — 2026-11-04

Commit `c9cb0a7` on `main` (pushed over HTTPS with the gh login; SSH push was refused with "Permission denied (publickey)" — the owner should check their SSH key). Site republished. CI on GitHub: build/test/docs/gates success, browser success. Live amiagentready 90 (11 pass, 3 warn, 0 fail). Playwright against production 43/43. Nothing in flight. Re-verified at `e3519d4` (2026-11-04): `pnpm check` green (887/887, 100% coverage, agent-ready, vocabulary clean, slop 100); Playwright 43/43 locally and 43/43 against production; live amiagentready 90 (12 pass, 3 warn, 1 info, 0 fail); CI on `7cecea2` and `76be6dd` success; `pnpm site:publish` re-run (agent-ready 22/22, published https://kernlize.github.io/).

## 2026-11-04 (later): kehto 0.32 adoption, kernl.run, kehto #336

- **kehto seams adopted** on branch `feat/kehto-0-32`: `@kehto/shell` 0.32.0, `@kehto/runtime` 0.28.0, `@kehto/firewall` 0.7.0 (kehto/web #404, released in #409; issues #397–#400 closed). `filterNappletMessage` replaces `refuseCurrentDispatch`; hidden windows' requests are held and released on visibility; `aclDefaultPolicy: 'restrictive'`; focus and profile budgets reach the runtime. D-020. Behaviour changes: dropped requests get no reply (spec gap), strict startup burst is 8 ops in the first second.
- **MUST TRACK kehto/web #336** (`feat/firewall-approvals`): Firewall becomes the shared approval authority; rate limits move to `runtime.permissions.setRateLimit`/`onRateLimit`; `setUnfocusedMultiplier`/`setBurstGuard`/`onFirewallEvent`/`firewallPersistence` become legacy-only on configured hosts. All of kernl's uses sit in `packages/core/src/guard/runtime-rules.ts`. When it merges and releases, migrate that file, drop `kernl:firewall:v1`, decide how admission approvals meet the restrictive ACL, update D-020 and the roadmap.
- **kernl.run** on bunny.net: PR #2 merged (`47c13b7`). Site `kernl` exists, secrets set by `scripts/setup-bunny-secrets.sh`. Owner: point the `kernl.run` apex (Porkbun) at `sites-kernl-0bmwsx.b-cdn.net` with ALIAS, then `bunny sites domains ssl kernl.run kernl`. Follow-up: port napplet's `edge/` Markdown-negotiation middleware (expected amiagentready 90 → 92).
- **Release:** `@kernl/*` 0.2.0 versioned on `main` (PR #1); not on npm until a `v*` tag or a manual `release.yml` run.
- **Open from the kehto adoption:** offline preview's guard never holds (only quarantines); README guard screenshot and alt text show the old behaviour; `packages/core/features/steps/kernl.ts` is 652 lines (over the 600 guideline, not flagged by the slop gate).
27 changes: 27 additions & 0 deletions DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Format: `D-NNN · title` → Context, Decision, Consequences, Reversal cost.

**Reversal cost.** Low. Both local layers sit behind interfaces in `@kernl/guard` and `@kernl/core` and can be deleted when upstream provides the hook.

**Superseded in part by D-020.** All four gaps are closed in `@kehto/shell` 0.32.0, `@kehto/runtime` 0.28.0 and `@kehto/firewall` 0.7.0. The restrictive posture now comes from `aclDefaultPolicy`, and the runtime firewall receives focus, the profile's multiplier and burst guard, and an event sink. The focus-aware firewall in `@kernl/guard` stays, for the reasons D-020 gives.

## D-002 · applesauce v7 is the only event layer

**Context.** kehto's reference runtime (Paja) uses `nostr-tools` `SimplePool`. kernl's first opinion is that applesauce is the event layer.
Expand All @@ -41,6 +43,8 @@ Format: `D-NNN · title` → Context, Decision, Consequences, Reversal cost.

**Reversal cost.** A one-line profile change for a user; a `SOUL.md` amendment for the project.

**Superseded in part by D-020.** A profile change now reinstalls the runtime rates of every open window and sets the runtime's unfocused multiplier and startup burst guard, so open windows no longer keep the rates they were mounted with.

## D-004 · Four packages, one app, one docs site

**Context.** The repository needs a shape that lets docs, tests and publishing stay per-package without forcing the shell UI into a framework-free constraint.
Expand Down Expand Up @@ -148,6 +152,8 @@ The window-level `hello` is the one message kernl can hold or drop before kehto

**Reversal cost.** Low; the pipeline sits behind `createGuardPipeline` and shrinks when kehto exposes a hook that can hold or drop an envelope. `refuseCurrentDispatch` goes when kehto offers that hook and its runtime can measure the envelopes and focus it cannot see today.

**Superseded in part by D-020.** kehto now offers that hook. Port traffic is decided by `ShellAdapter.filterNappletMessage`: what kernl's guard will not let through is dropped rather than refused, a hidden window's requests are held and released when it is visible, and `refuseCurrentDispatch` is deleted. The runtime firewall now applies the unfocused multiplier and reports its refusals to the guard log. The size, burst, focus and quarantine layers described above remain kernl's.

## D-013 · The site is served at a domain root

**Context.** D-007 sets an agent-readiness score of 84 or higher. Measured with amiagentready on 2026-11-03, the scanner works on the **origin**: it scores the domain's homepage whatever path it is given, and reads `robots.txt` and `llms.txt` from the domain root. The scoring is reproducible from its response: each scored check contributes its weight, pass at full, warn at half, fail at zero.
Expand Down Expand Up @@ -254,3 +260,24 @@ NZB, Soulseek and mail stay hand-off. Running them inside kernl needs something
**Consequences.** Without the secret the workflow builds, audits and stops with a notice, as before. Rolling back is `bunny sites deployments publish --previous --force`. The account API key in the secret can manage every bunny.net resource on the account, not only this site; bunny.net offers no narrower key for site deploys. The pull zone does not yet answer `Accept: text/markdown` (D-013's 92 needs a middleware Edge Script implementing `scripts/serve-pages.mjs`'s rule), so the expected score at kernl.run is 90 until then. `kernlize.github.io` is no longer updated and can be retired.

**Reversal cost.** Low; only deployment configuration and the default origin.

## D-020 · kehto's host seams replace kernl's workarounds; kernl's guard still decides first

**Context.** D-001 recorded four kehto gaps (kehto/web #397, #398, #399, #400) and D-012 built kernl's enforcement around them. kehto/web PR #409 closed all four. `@kehto/shell` 0.32.0 forwards `getFocusContext`, `onFirewallEvent` and `firewallPersistence` through `ShellAdapter`, takes `aclDefaultPolicy`, and adds `filterNappletMessage`, which answers `dispatch`, `drop` or `defer` for each port envelope, with `ShellBridge.releaseDeferred(windowId)` and a new revocation reason, `deferred-overflow`, for a window holding more than `MAX_DEFERRED_MESSAGES` (1024). `@kehto/runtime` 0.28.0 lets a policy the host sets replace the stored one on load. `@kehto/firewall` 0.7.0 adds `setUnfocusedMultiplier` and `setBurstGuard`, also on `FirewallStateContainer`. A second change is pending: [kehto/web#336](https://github.com/kehto/web/pull/336) makes the firewall a shared approval authority and moves rate limiting into a separate runtime budget. On a host that configures `permissions` it ignores `setUnfocusedMultiplier`, `setBurstGuard`, `onFirewallEvent` and `firewallPersistence`, reads only `focused` from `getFocusContext`, and keeps `aclDefaultPolicy`, `filterNappletMessage` and `releaseDeferred` unchanged.

**Decision.** kernl depends on the released packages: `@kehto/shell` ^0.32.0, `@kehto/runtime` ^0.28.0, `@kehto/firewall` ^0.7.0, `@kehto/acl` ^0.20.3 and `@kehto/services` ^0.30.1, which requires `@napplet/core` 0.38 and `@napplet/nap` 0.37. It builds first on the seams that survive #336:

- **Port traffic is decided before the runtime sees it.** `createShellAdapter` passes `filterNappletMessage`, which runs `GuardPipeline.observe` and returns kehto's decision. `refuseCurrentDispatch` and kernl's use of `onNappletMessage` are deleted. What kernl's guard will not let through is dropped: an oversized download or query, a burst above the per-second rate, a request over the focus firewall's budget, and the envelope that triggers a quarantine. A dropped request gets no reply, which is kehto's documented spec gap (NAP defines no answer for a request a host drops); before, the applet got a `firewall:` error.
- **Hidden windows are held.** A non-control envelope from a window that is not visible is deferred, reported as a `defer` guard event `hidden:<class>`, and counted against `deferQueue`; past it, it is dropped with `defer:queue-full`. When the window becomes visible the pipeline calls `onRelease`, and kernl calls `bridge.releaseDeferred(windowId)`; kehto dispatches the held envelopes in order through the ACL and the runtime firewall. A hidden download past `hiddenResourceAllowance` still quarantines, and kehto discards what the window held. `deferQueue` (200 in `strict`, 600 in `relaxed`) is below `MAX_DEFERRED_MESSAGES`, so kernl drops before kehto would revoke; `deferred-overflow` is still handled the way every revocation is, by removing the frame. kehto queues every later envelope behind held ones, so once a hidden window holds anything its control requests wait too.
- **The ACL is restrictive.** The adapter sets `aclDefaultPolicy: 'restrictive'`. `KernlGrants.ensure` grants the baseline and what the user allowed, and revokes only what the applet's stored entry holds beyond that (a domain the user took back, or an entry kept from the permissive posture), instead of writing every capability. The environment resolver still narrows what is advertised. Nothing is granted until the user grants it.
- **The runtime firewall knows focus and the profile.** `getFocusContext` returns `{ focused }` from the pipeline's visibility; it does not send `msSinceFocusGain`, which #336 ignores. On boot and on every profile change, `applyRuntimeFirewallProfile` sets the profile's `unfocusedMultiplier` and `burstGuard` (`{ windowMs: 1000, maxOps: ingressBurst, action: 'block' }`) and kernl reinstalls the rates of every open window. The runtime's startup burst is now the profile's, 8 operations in a window's first second in `strict`, instead of kehto's default of 20 in 3 s.
- **Runtime refusals reach the guard log without the message.** `createRuntimeFirewallHooks` turns each `onFirewallEvent` into a `GuardEvent` (`rate:<class>`, `flood:startup`, or `runtime:<ruleId>`) and drops the applet message the runtime attaches, so nothing the applet sent reaches the log. It keeps the runtime firewall's configuration under `kernl:firewall:v1` in the host's storage. These report only what the runtime itself refuses: the startup burst, and held envelopes over budget at release. Everything kernl decides is reported by kernl's own layers, so the log does not depend on these hooks.
- **The seams #336 retires live in one module.** Every call into the runtime firewall's configuration (`setRateLimit`, `setGlobalRate`, `setUnfocusedMultiplier`, `setBurstGuard`, `persist`) and both hooks are in `packages/core/src/guard/runtime-rules.ts`.

**What kernl still puts in front of kehto, and why.** The ingress guard, the visibility gate and the focus firewall stay. kehto's runtime measures only an envelope's `event`, so size caps on downloads and queries are kernl's. It knows nothing of visibility, so holding hidden windows and the hidden download allowance are kernl's. Flood quarantine is kernl's. The window-level `hello` still passes the pipeline before `bridge.handleMessage`. The focus firewall now overlaps the runtime, which applies the same rates and multiplier, and it stays for three reasons: it has a separate budget for deletions, which the runtime counts as `relay:write`; its matchers can use `msSinceFocusGain`, which #336's budgets ignore; and once #336 lands a configured host gets no runtime event for a stored deny and only `onRateLimit` for budgets, so kernl's own decisions must stay a complete source of guard events.

**When kehto/web#336 lands.** In `runtime-rules.ts`, `applyRuntimeFirewallRules` and `applyRuntimeFirewallProfile` become one call to `runtime.permissions.setRateLimit`, built from the current `rateLimit`; `onFirewallEvent` becomes `onRateLimit`; `firewallPersistence` and `kernl:firewall:v1` go. `getFocusContext`, `aclDefaultPolicy`, `filterNappletMessage` and `releaseDeferred` stay as they are. kernl will also have to decide how admission approvals meet the restrictive ACL, which #336 treats as a ceiling.

**Consequences.** An applet whose request kernl drops waits for a reply that does not come, and has to time out. Because the focus firewall counts the same rates before the runtime does, a request over a class budget is normally dropped by kernl rather than refused by the runtime with a `firewall:` error; the runtime's own refusals are met mostly in a window's first second and for held requests released over budget. A hidden applet no longer has to retry. Held requests meet the runtime firewall at release, at the window's focus at that moment. A real applet that sends more than 8 operations, control included, in its first second under `strict` has the excess refused by the runtime with `firewall:` errors. `@kernl/core` drops `refuseCurrentDispatch` and changes `GuardPipeline.observe` and `ShellAdapterDeps`, which is a minor release on 0.x.

**Reversal cost.** Low. The decisions sit behind `GuardPipeline.observe` and `createShellAdapter`; the runtime firewall calls sit in one module.
Loading
Loading