4828b4a9e2
Review point from keeltrace, and it is a real gap: secret redaction and prompt omission are different contracts, and only the first one is pattern-shaped. redact_sensitive_text removes credentials. A provider 4xx that quotes the request back carries the user's own prose - a paragraph about a person, a file pulled in by an @ reference - which matches no credential pattern and so passed through untouched into cause=. The record's stated contract is that prompt content is not logged, and the previous commit only enforced the half of it that a regex can see. The existing prompt test could not catch this: its provider error does not echo the prompt, so it proves the prompt is not logged directly, not that it cannot arrive by being quoted. _strip_prompt_echo closes the quoted path directly. Anything the message shares with the submitted prompt for 24 characters or more becomes <prompt>. Shingle-set matching rather than a diff, so cost is linear in both strings on a path that runs for every failed turn and can face an @-expanded prompt of arbitrary size; the JSON-escaped form of the prompt is shingled too, because a provider handing back its own request body often hands it back escaped. The prompt is captured after @-expansion on purpose: an injected file's contents are exactly the material an echo would carry, and they are not in the submitted text. Ordering is load-bearing. The strip runs after the whitespace collapse, so a re-wrapped quote still matches, and before the length cap, so a quote cannot survive by being cut mid-run. What this does not claim: verbatim echo is what it stops. A paraphrase, a summary, or a re-encoding would survive it. The alternative keeltrace raised - log only structured provider metadata and drop the message body - is airtight but costs the diagnosis this PR exists to enable, since the reporter needed to tell a 402 from a crashed finalizer. Happy to switch if maintainers prefer the stricter contract. Tests: the non-secret sentinel keeltrace asked for (a benign phrase present only in the prompt, echoed by the provider error, asserted absent from the record), plus guards that a message sharing nothing with the prompt is untouched, that an overlap below the window is not treated as an echo, that a prompt shorter than the window cannot blank the message, that a JSON-escaped echo is stripped, that the strip precedes the length cap, and that whitespace shape does not hide an echo. The three that cover the new path fail with the strip removed; the guards pass either way.