Professional README + structured docs (with dedicated E2E & pairing guides) - #5
Merged
Merged
Conversation
…iring guides Rewrite the README to a polished, scannable overview (logo, badges, architecture mermaid + layer table, a real quick-start now that @telecode/cli is published) and restructure docs/ around a grouped index (Use it / Understand it / Operate it). Add two dedicated, plain-language, diagram-rich guides the trust model deserves: - docs/end-to-end-encryption.md — the three keys, the X25519 ECDH handshake, a message's round trip, and exactly what the relay can and cannot see. - docs/connecting-your-machine.md — outbound-only connections, sign-in identity, and how the device-authorization grant binds a machine to exactly the signed-in user (server-derived approval). Cross-link both from the threat model.
PouyanJay
added a commit
that referenced
this pull request
Jun 30, 2026
…adlock (Journey 1, Task 8) Two corrections to the T8 wiring: 1. Invariant #5 — adopted sessions now run end-to-end encrypted: the daemon establishes a per-session content key on adoption (gated by cipher.enabled, as every paired daemon is), so the relay forwards only ciphertext for adopted-session frames, not plaintext. The key is delivered to the browser on session.subscribe (the existing reconnect path). Cleartext only on a pre-E2E daemon (tests). 2. stop() deadlock — stop() awaited hookSocket.stop() (which waited on the in-flight bridge connection) BEFORE settling pendingPermissions (what unblocks it). Reorder: settle the gates first, then stop the socket; and the hook socket now force-closes lingering connections so a blocked gate can never hang shutdown. Adds an E2E test asserting the relay sees a non-empty nonce + ciphertext (not the cleartext gate payload). 127 daemon tests pass, no unhandled errors.
PouyanJay
added a commit
that referenced
this pull request
Jun 30, 2026
…adlock (Journey 1, Task 8) Two corrections to the T8 wiring: 1. Invariant #5 — adopted sessions now run end-to-end encrypted: the daemon establishes a per-session content key on adoption (gated by cipher.enabled, as every paired daemon is), so the relay forwards only ciphertext for adopted-session frames, not plaintext. The key is delivered to the browser on session.subscribe (the existing reconnect path). Cleartext only on a pre-E2E daemon (tests). 2. stop() deadlock — stop() awaited hookSocket.stop() (which waited on the in-flight bridge connection) BEFORE settling pendingPermissions (what unblocks it). Reorder: settle the gates first, then stop the socket; and the hook socket now force-closes lingering connections so a blocked gate can never hang shutdown. Adds an E2E test asserting the relay sees a non-empty nonce + ciphertext (not the cleartext gate payload). 127 daemon tests pass, no unhandled errors.
PouyanJay
added a commit
that referenced
this pull request
Jun 30, 2026
…, Task 6) Final journey task — parametrize the adopted-question deny-feedback path over the answer shapes Claude Code produces (single-select, multi-select, 'Other' free text, multiple questions in one call), each asserting the relayed pick lands in the deny reason. Plus an explicit invariant-#5 test that agent.question reaches the relay as ciphertext, never the cleartext questions. 142 daemon tests pass.
PouyanJay
added a commit
that referenced
this pull request
Jun 30, 2026
…rney 3, Task 5) Let the per-machine adoption policy (enabled + denylist) be managed from the web and enforced by the daemon at runtime, end-to-end encrypted so the relay never sees repo paths (invariant #5): - protocol: adopt.config (web -> daemon; { set? } — set persists, omit = read) + adopt.state (daemon -> web; current { enabled, denylist }) + adoptSettingsSchema. Both session-less, so the relay forwards them generically, payload-blind (no relay change). - daemon cipher: openFromBrowser / sealToBrowser — the same ECDH device-shared-key seam as decryptLaunch / keyDelivery, session-less. - adopt-config.ts store (~/.telecode/adopt-config.json, 0600, zod, adopt- all default on missing/corrupt) + is-adoption-allowed.ts pure matcher (enabled AND cwd not under any denylist path-prefix; siblings sharing a prefix are NOT blocked). - daemon: load the policy on start; handleAdoptConfig opens the sealed config, persists+applies a set, and replies adopt.state sealed to the requesting browser (logs the shape, never the paths); a runtime gate in handleHookEvent leaves a disabled/denied session entirely to Claude Code's local flow (no adopt, no mirror, no gate). Tests: adopt-config store + matcher units; daemon integration (SET persists + replies; GET replies; denylist blocks adoption; disabled blocks; re-enable adopts). 161 daemon + 88 protocol tests pass. (Web store/client + Settings UI = Task 6; E2E adopt.config ciphertext = Task 8.)
PouyanJay
added a commit
that referenced
this pull request
Jun 30, 2026
…3, Task 8) Prove invariant #5 for the adoption policy: a browser box-seals adopt.config to the daemon's key; the daemon opens it and replies adopt.state as opaque ciphertext (non-empty nonce, string payload) which only the holder of the device shared key can open — so the denylist's repo paths never reach the relay in cleartext. Completes the variant coverage (matcher / Notification / SessionEnd / config set-get-deny-disable already covered).
PouyanJay
added a commit
that referenced
this pull request
Jul 3, 2026
… 4, Task 6) The forked continuation is now navigably linked to the adopted session it continues, and the offer's payload is proven ciphertext to the relay. - daemon test: agent.handover reaches the relay as ciphertext (non-empty nonce, string payload) on a keypair daemon — the question + summary never go cleartext (invariant #5) - web: reducer gains handover.childSessionId + SessionState.parentSessionId + linkHandoverChild; the store handles session.chained channel-wide to link parent and child across sessions; relay-api surfaces parent_session_id from /me/sessions - HandoverCard: "View the continuation →" link once taken over; the session detail page shows "← Continued from an adopted session" for a continuation - tests: reducer link + store cross-session link Gates green: daemon 192, web 236, svelte-check 0, typecheck 5/5, lint, format.
This was referenced Jul 3, 2026
PouyanJay
added a commit
that referenced
this pull request
Jul 5, 2026
…ssionEnd mirror TypeScript review: handleSessionEndHook mirrored the transcript BEFORE cipher.establish, so a session ending without a prior Stop could stream its final session.history in cleartext (E2E invariant #5). Move establish above the mirror, matching the PreToolUse + Stop hooks. Also clarify that the Stop mirror is intentionally unconditional (only the handover OFFER is policy-gated).
PouyanJay
added a commit
that referenced
this pull request
Jul 6, 2026
…he model (session-identity T5) Adopted and chained-continuation announces are now ids-only routing: an external session's project name/path and a continuation's derived title never reach the relay in cleartext (the P1-2 privacy fix for invariant #5). title/cwd removed from the AdoptInput and registerChained TYPES, not just the calls — a future re-leak is a compile error. The identity travels in a sealed session.meta emitted AFTER cipher.establish at every site: adopted = cwd basename, refined to the first prompt; chained = "Continue: <question>". The model is captured from the SDK system/init through AgentRunResult.model (kept behind the AgentAdapter seam) and emitted on-change only. Web SessionRail gains Model + Directory rows, reusing the existing meta-row pattern and tokens. Tests: two keypair-based ciphertext-proof tests (adopted via SessionStart; chained via a full browser E2E round-trip — subscribe, unwrap the content key, decrypt the sealed handover offer, seal the answer, then assert the child meta is opaque), plus model-captured/absent and a no-re-emit-on-unchanged-model barrier that drains to session.ended and fails fast naming any stray frame. derivedMetaPatch gains direct unit coverage including the empty-title (root/blank cwd) case — basename('/') is '' and would violate the wire schema's title.min(1), so it is dropped rather than emitted.
PouyanJay
added a commit
that referenced
this pull request
Jul 6, 2026
…+ endpoints (session-identity T6a)
A session rename is a browser-sealed REST PATCH, kept in a blob separate from
the daemon-owned identity so a later derived title can never clobber it.
- Migration 0009 adds bounded sealed_title / sealed_title_nonce columns
(mirrors 0008); a shared sealed-blob-bounds module is now the single ceiling
for both sealed columns, so the route zod and the DB CHECK can't drift.
- New relay-only session.title wire type. PATCH /me/sessions/:id takes a union
body (a sealed SET blob, or null to reset-to-derived), writes it RLS-scoped
via setSealedTitle, and broadcasts session.title on the session's device
channel so every open tab updates live: a SET forwards the opaque
ciphertext + nonce verbatim, a RESET carries the cleartext {reset:true}
marker. The relay never reads a set title (invariant #5).
- session.title is added to both the browser and daemon forgery drop-lists: a
rename must go through the persisting, bounded REST path, never a raw frame
that would fall through to an unbounded verbatim broadcast.
- PATCH /me/devices/:id renames a device by its cleartext name (hostnames are
already stored/served cleartext), active devices only.
- GET /me/sessions now returns the sealed override for cold loads.
Tested against a real relay + Postgres: set/reset persist + broadcast, the
cold-load GET, RLS cross-tenant isolation (victim row untouched), bounds and
malformed-body rejection, and the daemon-forgery drop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings the repo's docs up to a polished, professional bar — a scannable README and a structured
docs/folder, with two dedicated, plain-language, diagram-rich guides for the parts of the trust model that matter most.README
@telecode/cli, stack, E2E), and quick-nav.curl … | bashandnpx @telecode/cli.docs/
Grouped index (Use it / Understand it / Operate it), mirroring a clean flat-folder + grouped-index layout. Two new guides:
docs/end-to-end-encryption.md— in plain language with diagrams: the three keys (device, browser non-extractable, per-session content key), the X25519 → ECDH → HKDF → AES-256-GCM handshake, a message's round trip, and a clear "what the relay sees / never sees" table. Built onpackages/protocol/src/webcrypto.ts.docs/connecting-your-machine.md— how your browser reaches a laptop with no open ports (outbound-only), how sign-in identity works (GitHub OAuth +httpOnlycookie + signed channel token), and — the crux — how the device-authorization grant binds a machine to exactly the signed-in user (approval is server-derived; the client never supplies a user id). Built on the RFC 8628 flow inpackages/{protocol,daemon}andapps/relay.Both are cross-linked from
docs/threat-model.md.Notes
docs/(notdocumentation/): it's already referenced across CLAUDE.md, deploy scripts,install.sh, and code comments — renaming would break links for no real gain. "Structured" is delivered via the grouped index + clear naming.pnpm format:checkpasses; all internal links and referenced code paths verified to exist.