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:
parent
a8fd510dc7
commit
61bdc5360f
3 changed files with 69 additions and 4 deletions
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue