diff --git a/.planning/todos/pending/2026-08-06-aiui-answers-in-prose-instead-of-the-content-and-context-surfaces.md b/.planning/todos/pending/2026-08-06-aiui-answers-in-prose-instead-of-the-content-and-context-surfaces.md index 2b7b7ce8..8db8e83b 100644 --- a/.planning/todos/pending/2026-08-06-aiui-answers-in-prose-instead-of-the-content-and-context-surfaces.md +++ b/.planning/todos/pending/2026-08-06-aiui-answers-in-prose-instead-of-the-content-and-context-surfaces.md @@ -39,6 +39,26 @@ Only 1 of 10 turns used a surface. The content is correct in every case; the content-card parser (title *n* paired with description *n−1*). - A markdown table of 14 apps is unreadable on mobile; the app grid is responsive. +## The rendering contract (operator, 2026-08-06, second note) + +> "the chat rich content often overflows and should always be a mini version where +> content surface and context windows expand on that version." + +So this is not "move everything out of the chat" — it is a **two-tier contract**, and +it applies to rich content the chat *already* renders as well as the prose above: + +- **In the chat: always the mini version.** Compact, bounded height, never overflows + the bubble — a summary chip/card. 14 apps become one "14 apps, all healthy" tile, + not a 14-row table. A config file becomes a named file chip, not 30 lines in a fence. +- **The content surface and context window are the expansion target.** Tapping the + mini version (or the intent itself) opens the full grid / viewer / dashboard there. +- Overflow in the chat is the symptom to test against: nothing rich should be able to + blow out the bubble at any viewport. Check mobile first — the 14-row markdown table + is the worst current offender. + +Applies to every row of the table above: pick the mini form AND its expanded surface +for each intent, rather than treating them as separate designs. + ## Security defect found in the same transcript — fix regardless The `bitcoin.conf` answer printed **`rpcpassword=archipelago123` in cleartext** into @@ -47,27 +67,41 @@ config file rendered by the assistant must be **redacted before it reaches the m or the transcript** — `rpcpassword`, `rpcauth`, tokens, keys, mnemonics. This is the phase's own non-negotiable (keys out of the browser and the model). -Related and currently RED — likely the same gap, and a real failing test on main: +A test was RED alongside this — **it was a stale fixture, not a live hole**, and it is +now fixed. Recording the correction because the first read of it was wrong: assistant::backends::routstr::tests::secret_shaped_content_never_reaches_the_stub panicked at archipelago/src/assistant/backends/routstr.rs:1073 "a secret-shaped body must be blocked" -`screen_outbound` (`assistant/egress.rs:411`) is NOT blocking a 12-word BIP-39-shaped -seed in a user turn on the Routstr paid leg, so it would be sent to a third-party -inference provider. Pre-existing, from 13-13 — confirmed unrelated to the appgate -Authorization fix (that commit touched only `appgate/mod.rs`; `routstr.rs` has no -reference to appgate). 1300 passed / 1 failed. +`screen_outbound` was rewritten for precision on 2026-08-06 to validate the **BIP-39 +checksum** instead of matching a word-run shape — the shape rule had blocked every +legitimate turn on a live node, twice. `egress.rs`'s own test was updated to a +checksum-valid mnemonic; this routstr copy still used the first twelve wordlist +entries, which is not a parseable mnemonic, so it asserted behaviour that had been +deliberately retired. A **real** mnemonic is still blocked on the Routstr paid leg +(`screen_outbound` runs at `routstr.rs:516`, before any body is sent), and +checksum-invalid runs of 20+ wordlist members are still caught by +`IMPLAUSIBLE_MEMBER_RUN`. Fixture corrected to a checksum-valid mnemonic. + +Pre-existing, from 13-13 — unrelated to the appgate Authorization fix (that commit +touched only `appgate/mod.rs`; `routstr.rs` has no reference to appgate). +1300 passed / 1 failed → now green. ## Suggested shape of the work -1. Fix `screen_outbound` first — it is a live security hole with a failing test. -2. Redact secrets in file/config rendering, on the node side, before the model sees it. -3. Audit which intents already have a surface and are simply not being routed to it - vs. which have no surface yet. Ties directly to the open item "audit all ten +1. Redact secrets in file/config rendering, node-side, before the model or the + transcript sees it. `rpcpassword` in cleartext is the proof it is not covered — + `screen_outbound` guards the *outbound cloud leg*, not what gets rendered back. +2. For each intent in the table: choose its **mini** form (chat) and its **expanded** + surface (content/context), per the rendering contract above. Both, together. +3. Audit which intents already have a surface and are simply not routed to it vs. + which have no surface yet. Ties directly to the open item "audit all ten `AIContextCategory` values in `fetchAndSanitize` for real coverage, not stubs". 4. Make structured model output the contract for these intents rather than parsing prose back into cards — the same conclusion the content-card parser item reached. +5. Test overflow at mobile viewport for every mini form; the 14-row table is the + current worst case. ## Loop diff --git a/core/archipelago/src/assistant/backends/routstr.rs b/core/archipelago/src/assistant/backends/routstr.rs index 5bb174e2..82c0add7 100644 --- a/core/archipelago/src/assistant/backends/routstr.rs +++ b/core/archipelago/src/assistant/backends/routstr.rs @@ -1052,8 +1052,22 @@ mod tests { async fn secret_shaped_content_never_reaches_the_stub() { let stub = StubRoutstr::start(chat_response_text("ok")).await; let backend = backend_for(&stub.base_url, 1_000); - let secret_seed = - "abandon ability able about above absent absorb abstract absurd abuse access accident"; + // A CHECKSUM-VALID mnemonic — what a real leak looks like, and what + // the screen actually keys on. The earlier fixture was the first + // twelve wordlist entries, which is not a parseable mnemonic: the + // 2026-08-06 precision rewrite of `screen_outbound` moved from a + // shape rule to checksum validation (the shape rule blocked every + // legitimate turn on a live node, twice), `egress.rs`'s own test was + // updated to match, and this copy was not — so it failed here while + // the behaviour it names was intact. Checksum-invalid runs are + // deliberately allowed below `IMPLAUSIBLE_MEMBER_RUN`; see + // `assistant::egress`. + let secret_seed = "abandon abandon abandon abandon abandon abandon \ + abandon abandon abandon abandon abandon about"; + assert!( + bip39::Mnemonic::parse_normalized(secret_seed).is_ok(), + "the fixture must be a real mnemonic or this test proves nothing" + ); let history = vec![ChatMessage { role: Role::User, text: Some(format!("my seed is: {secret_seed}")),