fix(13-13): assert the seed screen with a real mnemonic, not a stale shape fixture

secret_shaped_content_never_reaches_the_stub was RED because its fixture was
the first twelve wordlist entries — not a parseable mnemonic. The 2026-08-06
precision rewrite of screen_outbound moved from a word-run shape rule to BIP-39
checksum validation (the shape rule had blocked legitimate turns on a live node
twice); egress.rs's own test was updated to a checksum-valid fixture and this
copy was not, so it asserted behaviour that had been deliberately retired.

The named behaviour was intact throughout: screen_outbound runs on the Routstr
paid leg before any body is sent, a real mnemonic is blocked, and
checksum-invalid runs of 20+ wordlist members are still caught by
IMPLAUSIBLE_MEMBER_RUN. Fixture is now a checksum-valid mnemonic, asserted as
parseable so it cannot silently rot the same way again.

Also records the operator's rendering contract in the surfaces todo: chat gets
the mini version, the content/context surfaces expand it, nothing rich may
overflow the bubble at mobile width.
This commit is contained in:
archipelago
2026-08-06 20:16:48 -04:00
parent 36574c0230
commit 3d4d329787
2 changed files with 60 additions and 12 deletions
@@ -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 *n1*).
- 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
@@ -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}")),