Watch
1
0
Fork
You've already forked souveraine
0

session trust: step-up send gating + lock-time response redaction

Step-up send gating:
- Souveraine.send() returns 'step-up' when auth is required
- Ai.sendUserMessage() triggers StepUpAuth.requestAuth('send') on
  step-up, retries on success, shows message on failure
- Extracted _startStreaming() helper for reuse after auth retry

Lock-time response redaction:
- Ai.qml watches GlobalStates.screenLocked
- On lock mid-stream: replaces displayed content with '[content hidden
  until unlock]', preserves rawContent for post-unlock display
- Enforces SESSION-TRUST-ARCHITECTURE.md requirement: lock during
  personal agent output hides it

Trust boundary matrix updated.
This commit is contained in:
Fimeg 2026-07-14 21:13:50 -04:00
commit 61bdc5360f
3 changed files with 69 additions and 4 deletions

View file

@ -119,6 +119,31 @@ Singleton {
}
}
// Lock-time response redaction
// When the session locks during a personal-tier streaming response,
// the in-flight content must be withheld immediately. Only ambient
// output remains visible on the lock surface. This is the enforcement
// side of SESSION-TRUST-ARCHITECTURE.md's "A lock during personal
// agent output hides it" requirement.
//
// The streaming message is replaced with a redaction placeholder. The
// raw content is preserved in the message's rawContent so it can be
// shown again after unlock (the message stays in history), but the
// displayed content is cleared.
Connections {
target: GlobalStates
function onScreenLockedChanged() {
if (!GlobalStates.screenLocked) return;
if (!root.streamingMessage) return;
// Lock fired mid-stream. Redact the displayed content.
// rawContent preserves the actual response for post-unlock.
if (root.streamingMessage.content.length > 0) {
root.streamingMessage.content = Translation.tr("[content hidden until unlock]");
console.log("[ai] lock fired during stream — redacted personal output");
}
}
}
// Streaming message shaping
property AiMessageData streamingMessage
property bool inThinkBlock: false
@ -306,11 +331,37 @@ Singleton {
function sendUserMessage(message) {
if (message.length === 0) return;
root.addMessage(message, "user");
if (!Souveraine.send(message)) {
const result = Souveraine.send(message);
if (result === "step-up") {
// Step-up auth required. Trigger the PAM flow; on success,
// retry the send. The user message is already in the chat
// history, so we don't add it again.
if (typeof StepUpAuth !== "undefined") {
StepUpAuth.requestAuth("send", function(granted) {
if (granted) {
// Remove the "auth required" indicator if one was
// added, and retry. The queued text was not consumed
// by Souveraine, so we can send it again.
Souveraine.send(message);
// Re-create the streaming message for the response.
root._startStreaming();
} else {
root.addMessage(Translation.tr("Authentication required to send."), root.interfaceRole);
}
});
}
return;
}
if (!result) {
root.addMessage(Translation.tr("Souveraine server unreachable at %1 — start it with `souveraine server`").arg(Souveraine.serverBase), root.interfaceRole);
return;
}
/* Streaming assistant message; filled by handleStreamEvent */
root._startStreaming();
}
// Set up the streaming assistant message container. Called after a
// successful send (or after a step-up auth retry succeeds).
function _startStreaming() {
root.inThinkBlock = false;
root.streamingMessage = root.aiMessageComponent.createObject(root, {
"role": "assistant",