Repository navigation
Wiring: several outgoing transforms in a fixed order instead of one slot #893
Copy link
Copy link
Labels
Milestone
Description
Goal
An embedding app can register several outgoing transforms through Wiring. The core runs them in a fixed, documented order on the approved text, collects the secret values all of them inserted, and stops at the first rejection, so independent extensions no longer have to chain each other by hand.
Context
crates/app-shell/src/wiring.rs:66-69:pub outgoing_transform: Option<Arc<dyn OutgoingTransform>>– one slot.- Trait and types:
crates/core/src/outgoing.rs(OutgoingTransform::prepare_outgoing(scope, kind, text) -> Result<Prepared, Rejection>,Prepared { text: SecretString, secrets: Vec<SecretString> }with a redactingDebug). - The single caller:
prepare_for_sendincrates/app-logic/src/orchestration/outgoing.rs:29-80. It runs after filter, risk, second opinion and confirmation, re-checks a transformed command against the hard blacklist only if the original did not match (Outgoing transform: a confirmed hard-blacklist command is rejected after preparation #697), adds the inserted secrets to the session redactor before the command starts, and redacts a rejection message. - Plumbing keeps one
Option<Arc<dyn OutgoingTransform>>:crates/app-shell/src/lib.rs:120,151,194,408,556,crates/app-logic/src/state.rs:203,crates/app-logic/src/session.rs:888,crates/app-shell/src/commands/connect.rs:972. - Spec 0038, section "Umwandlung ausgehender Texte" describes one transform.
Requirements
Wiringgetspub outgoing_transforms: Vec<Arc<dyn OutgoingTransform>>(Community: empty). The oldoutgoing_transform: Option<…>stays for now (additive); when set it runs first. Its doc comment says it is superseded and will be removed in a later release.- New
CompositeOutgoingTransformincrates/core/src/outgoing.rsimplementingOutgoingTransform:- runs the transforms in registration order; transform n+1 receives the text produced by transform n (same
scopeandkind) - the result's
secretsare the union of all inserted secrets (order kept) - the first
Rejectionstops the chain; nothing of the partial result is returned or logged Debugof the composite and of intermediate results never shows text or secret valuesrequires_confirmation(added by Outgoing transform: confirmation query for texts it changes, and reject a prepared command with a different segment count #815, blocks this issue):trueif any transform answerstrue(fail-closed); every further trait method gets a fail-closed combination as well, and the spec states the rule
- runs the transforms in registration order; transform n+1 receives the text produced by transform n (same
app-shellbuilds the composite only when two or more transforms are registered; with zero the slot staysNone(Community path unchanged: no group-chain lookup, text sent as is); with one, that transform is passed through.prepare_for_sendis not changed: the hard-blacklist re-check, the redactor update and the rejection redaction apply to the final text of the chain, exactly once.- Spec 0038: "eine Umwandlung" becomes a list; document order, chaining, secret union, first rejection wins, re-check on the final text.
Acceptance Criteria
-
Wiring::outgoing_transformsexists;Wiring::community()leaves it empty and the session getsNone(test). - Unit tests for
CompositeOutgoingTransform(incore):- two transforms: the second sees the first one's output; final text is the second one's output
- secrets of both transforms are in
Prepared::secrets() - first transform rejects → second is never called, the rejection message is returned
- second rejects → its rejection is returned
-
format!("{:?}", …)of the composite result contains neither text nor secret values
- Test in
app-logicthroughprepare_for_sendwith two transforms: both inserted secrets are masked in the output of that very command; a chain whose final text matches the hard blacklist (original did not) is rejected withPREPARED_BLOCKED_MESSAGE. -
requires_confirmationof the composite:falseonly if all transforms answerfalse(test with two transforms, each answeringtruealone). - Setting only the old field behaves exactly as before (existing outgoing-transform tests unchanged and green, including Outgoing transform: a confirmed hard-blacklist command is rejected after preparation #697).
- Old field + one list entry: old one runs first (test).
- Spec 0038 updated.
- Relevant automated tests pass (
cargo test --workspace, clippy with-D warnings).
Technical Notes
- Security-critical: execution path and redaction. Read
.claude/skills/smart-ssh-coder/references/security-invariants.md. Filter, risk and confirmation keep seeing only the original text; the transformed text must not reach events, history, ledger or logs; nothing here may weaken the hard-blacklist re-check. Review priority is elevated. - Files:
crates/core/src/outgoing.rs,crates/app-shell/src/wiring.rs,crates/app-shell/src/lib.rs(:556), tests incrates/coreandcrates/app-logic/src/orchestration/,docs/specs/0038-repository-app-shell-ci.md. - Do not touch
FilterEngine::evaluate()or the risk classifier.
Spec impact: update 0038
Open questions
- none
Related Work
- Extension point: transform approved commands and file content right before sending #464 (outgoing transform extension point), Outgoing transform: pass the server's group chain like the session extension #501, Outgoing transform: a confirmed hard-blacklist command is rejected after preparation #697
- Outgoing transform: confirmation query for texts it changes, and reject a prepared command with a different segment count #815 (confirmation query and structural re-check for the outgoing transform; blocks this issue)
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
P2 - Normal
Activity
feat(app-shell): register several outgoing transforms in a fixed order (
fix(app-shell): keep lib.rs within its size limit (#893)
smart-ssh-coder commentedon Oct 27, 2026
Implementation ready for review. PR #993. Wiring::outgoing_transforms and CompositeOutgoingTransform implemented with tests and Spec 0038 update; PR #993 CI is green.
smart-ssh-reviewer commentedon Oct 27, 2026
Review passed – moving on to merge. Approved: composite outgoing transform and Wiring::outgoing_transforms verified; tests run locally and CI green, counter-proof done, spec 0038 updated.
feat(app-shell): several outgoing transforms in a fixed order (#993)
smart-ssh-coordinator commentedon Oct 27, 2026
Merged and branch deleted: PR #993 (squash) in develop