You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#634 fixed the case where the browser closed and keel serve kept running: the session cookie now carries a Max-Age, so a token that is still valid stops being thrown away. It deliberately did not fix the case where keel serve restarts, because that one is a theorem rather than a design choice — after a restart, no client can present a credential the new run accepts unless the server persisted something across the restart.
What ships today for that case is honesty: the console distinguishes a refusal from an outage, and offers a field to paste the address (or just the token) keel printed. That works from a browser tab and from an installed app whose shell is still cached.
The tail it does not reach, recorded in #634's commit and repeated here so it is not lost: an installed app whose service-worker cache has been evicted — Safari does this after seven unused days — makes a tokenless network navigation and gets the server's plain-text refusal in a window with no address bar. The text is truthful and names the token; there is nowhere in that window to paste one. That is the case pairing exists for.
#648 is the declared prerequisite for every Phase B item and says in terms that nothing remote ships before it. A pairing credential's whole value is remote — it is what lets a phone on a mesh re-authorise with no operator in the loop. Shipping a long-lived, remotely-usable bearer secret before the pass that defines token issuance, expiry and brute-force posture off loopback is shipping the prize before the lock.
And it would change what keel serve --host warns about. Today: "The session token is the only thing in the way, and it travels in cleartext" — and that token dies with the process. A pairing secret in cleartext on a LAN is a credential that does not. Inside a WireGuard mesh that is fine; on a plain --host LAN it is a real downgrade, and #648 is where the difference between those two gets written down.
The shape to build, so the design is not re-derived
The device holds the secret; keel stores only a verifier. A salted hash, ResolvedSecret's discipline applied to storage: reading keel's disk yields something that cannot be replayed. This is the whole reason pairing is better than An installed PWA cannot authorise itself after a serve restart — start_url carries no token #634's option 2 (a persisted server-side session secret), and not merely nicer to use — option 2 puts the live bearer token on disk in usable form for every client at once.
A roster file, not a schema change.state_root() from keel_core.paths, mode 0600. keel/data/db.py's SCHEMA_VERSION stays out of it.
Paired once, from a token-bearing URL. The credential is minted on a navigation that already authenticated; there is no second front door.
Revocation is a first-class gesture, not "delete the file if you can find it" — keel devices to list what is paired and when it last authenticated, and to forget one or all.
Never logged, never rendered, per keel_core.secrets.
Option 1 of #634, which #634 shipped without and named as the right answer in the wrong order.
What #634 left open
#634 fixed the case where the browser closed and
keel servekept running: the session cookie now carries aMax-Age, so a token that is still valid stops being thrown away. It deliberately did not fix the case wherekeel serverestarts, because that one is a theorem rather than a design choice — after a restart, no client can present a credential the new run accepts unless the server persisted something across the restart.What ships today for that case is honesty: the console distinguishes a refusal from an outage, and offers a field to paste the address (or just the token) keel printed. That works from a browser tab and from an installed app whose shell is still cached.
The tail it does not reach, recorded in #634's commit and repeated here so it is not lost: an installed app whose service-worker cache has been evicted — Safari does this after seven unused days — makes a tokenless network navigation and gets the server's plain-text refusal in a window with no address bar. The text is truthful and names the token; there is nowhere in that window to paste one. That is the case pairing exists for.
Why it was not built at #634
#648 is the declared prerequisite for every Phase B item and says in terms that nothing remote ships before it. A pairing credential's whole value is remote — it is what lets a phone on a mesh re-authorise with no operator in the loop. Shipping a long-lived, remotely-usable bearer secret before the pass that defines token issuance, expiry and brute-force posture off loopback is shipping the prize before the lock.
And it would change what
keel serve --hostwarns about. Today: "The session token is the only thing in the way, and it travels in cleartext" — and that token dies with the process. A pairing secret in cleartext on a LAN is a credential that does not. Inside a WireGuard mesh that is fine; on a plain--hostLAN it is a real downgrade, and #648 is where the difference between those two gets written down.The shape to build, so the design is not re-derived
ResolvedSecret's discipline applied to storage: reading keel's disk yields something that cannot be replayed. This is the whole reason pairing is better than An installed PWA cannot authorise itself after a serve restart — start_url carries no token #634's option 2 (a persisted server-side session secret), and not merely nicer to use — option 2 puts the live bearer token on disk in usable form for every client at once.state_root()fromkeel_core.paths, mode0600.keel/data/db.py'sSCHEMA_VERSIONstays out of it.keel devicesto list what is paired and when it last authenticated, and to forget one or all.keel_core.secrets.keel/capabilities.pyrow, and that is the test of the design. Pairing authenticates; it must not authorise anything the console could not already reach. If a row would be needed, the design is wrong — An installed PWA cannot authorise itself after a serve restart — start_url carries no token #634 states this and it carries over unchanged. The disjointness pin stays green.Acceptance
keel serverestart, reaches a usable state with no terminal and no address bar.keel deviceslists and revokes; revocation takes effect on the next request.--hostwarning is re-read against what this adds and is still true, or is rewritten to stay true.Blocked on #648. Follows #634.