Skip to main content

Session

Struct Session 

Source
pub struct Session { /* private fields */ }
Expand description

Owns the token manifest for one pseudonym namespace.

A Session holds the bidirectional map between PII values and their pseudonymous tokens. Create one per conversation and thread it through every Pipeline::redact call.

A Session is the boundary of a pseudonym namespace. Each new Session starts with fresh per-class counters (Person_1, Email_1, etc.) and a fresh session_hex prefix. Two Sessions never share next_by_class, token_by_value, or value_by_token, regardless of Scope variant.

The Scope variant chooses persistence, not isolation:

  • Scope::Ephemeral is process-bound. Its namespace lives until the Session is dropped. Use it for ad-hoc one-off redaction. Do not share one Ephemeral Session across logical conversations if those conversations should have independent counters and value-to-token mappings.
  • Scope::Conversation(String) is a keyed namespace that can be re-opened across process restarts when the adopter stores and imports the session snapshot. Use it for per-conversation chat or per-thread agents.
  • Scope::Persistent { .. } is a long-lived namespace with a TTL encoded in exported snapshots.

Picking the wrong variant does not break single-call redaction correctness, but it can produce cross-call linkability: identical raw values fed into one Session always map to the same pseudonym, so downstream consumers can link mentions across contexts that the adopter intended to keep independent.

§Restore workflow

  1. Call Pipeline::redact - returns a gaze_types::CleanDocument and updates the session map.
  2. Call Session::export to produce a SensitiveSnapshot.
  3. Persist the raw bytes (snapshot.into_bytes()) encrypted at rest - they contain original PII.
  4. Send only the cleaned text to the LLM.
  5. After the LLM responds, call Session::import with SensitiveSnapshot::from(bytes).
  6. For each token in the LLM response, call Session::restore_strict (or Session::restore).

There is no Pipeline::restore_text method - full-text restore is performed by scanning tokens with crate::token_shape::pattern and calling restore_strict per token.

Document workflows use the same restore root. Call Session::export_with_extension only when writing a gaze-document bundle that needs signed integrity hashes and codec provenance; plain text adopters should keep using Session::export.

§Round-trip example

use gaze::{
    token_shape, Action, ClassRule, CleanDocument, DefaultRule, Detection, Detector, PiiClass,
    Pipeline, RawDocument, Scope, SensitiveSnapshot, Session,
};

struct ExampleEmailDetector;

impl Detector for ExampleEmailDetector {
    fn detect(&self, input: &str) -> Vec<Detection> {
        let email = "alice@example.invalid"; // fixture-cited(crates/gaze/src/session.rs:session::tests::snapshot_round_trip_two_families_same_class_raw_preserved_under_shared_counter)
        input
            .find(email)
            .map(|start| Detection::new(start..start + email.len(), PiiClass::Email, "docs"))
            .into_iter()
            .collect()
    }
}

let pipeline = Pipeline::builder()
    .detector(ExampleEmailDetector)
    .rule(ClassRule::new(PiiClass::Email, Action::Tokenize))
    .rule(DefaultRule::new(Action::Preserve))
    .build()?;
let session = Session::new(Scope::Conversation("conv-1".into()))?;

let CleanDocument::Text(clean) = pipeline.redact(
    &session,
    RawDocument::Text("alice@example.invalid".into()), // fixture-cited(crates/gaze/src/session.rs:session::tests::snapshot_round_trip_two_families_same_class_raw_preserved_under_shared_counter)
)? else {
    panic!("text variant expected");
};

// Export before sending clean text to the LLM. Persist `blob` encrypted at rest.
let snapshot = session.export()?;
let blob: Vec<u8> = snapshot.into_bytes();

// Restore on the owner side after the LLM responds.
let restored_session = Session::import(SensitiveSnapshot::from(blob))?;
let mut restored = String::new();
let mut last = 0;
for m in token_shape::pattern().find_iter(&clean) {
    restored.push_str(&clean[last..m.start()]);
    restored.push_str(&restored_session.restore_strict(m.as_str())?);
    last = m.end();
}
restored.push_str(&clean[last..]);
assert_eq!(restored, "alice@example.invalid"); // fixture-cited(crates/gaze/src/session.rs:session::tests::snapshot_round_trip_two_families_same_class_raw_preserved_under_shared_counter)

See: docs/explanation/core/session-contract.md for the full architecture contract.

Implementations§

Source§

impl Session

Source

pub fn new(scope: Scope) -> Result<Self>

Source

pub fn from_policy(policy: &Policy) -> Result<Self>

Source

pub fn from_policy_with_ttl_override( policy: &Policy, ttl_secs_override: Option<u64>, ) -> Result<Self>

Source

pub fn tokenize(&self, class: &PiiClass, raw: &str) -> Result<String>

Source

pub fn tokenize_with_family( &self, family: &str, class: &PiiClass, raw: &str, ) -> Result<String>

Source

pub fn format_preserving_fake( &self, class: &PiiClass, raw: &str, ) -> Result<String>

Source

pub fn tokens(&self) -> Vec<String>

Enumerate every live token string emitted by this session.

Intended for restore-side callers that need to build an exact-literal alternation regex over the session map (Pass 1 of the two-pass restore strategy): replacing token-shaped strings via a class-shape regex alone is unsafe because it either (a) straddles word boundaries into adjacent text, or (b) misses lowercase FormatPreserve shapes like location_1. Feeding these exact strings into regex::escape and sorting longest-first avoids both pitfalls.

Returned order is unspecified — callers that rely on longest-first matching must sort the returned vector themselves.

Source

pub fn contains_token(&self, token: &str) -> bool

Source

pub fn session_hex(&self) -> String

Source

pub fn audit_session_id(&self) -> &str

Source

pub fn snapshot_entries(&self) -> Vec<SessionSnapshotEntry>

Source

pub fn restore_strict(&self, token: &str) -> Result<String>

Source

pub fn restore_strict_text(&self, text: &str) -> Result<String, RestoreError>

Source

pub fn restore_strict_text_with_events( &self, text: &str, ) -> Result<(String, Vec<RestoreEvent>), RestoreError>

Source

pub fn restore_boundary_events(&self, text: &str) -> Vec<RestoreEvent>

Source

pub fn restore(&self, token: &str) -> Option<String>

Source

pub fn export(&self) -> Result<SensitiveSnapshot>

Source

pub fn export_with_extension( &self, extension: DocumentExtension, ) -> Result<SensitiveSnapshot>

Export a document-extended snapshot for gaze-document bundle manifests.

Use this instead of Session::export when writing a document bundle with <base>-agent/ files (clean.md, layout.json, report.json, optional preview-redacted.png) plus owner-only <base>-owner/manifest.bin. The supplied DocumentExtension is serialized inside the signed snapshot payload, so its hashes and codec audit rows become the integrity root for the agent-facing files. Text-only adopters should use Session::export. Current snapshots emit v5 so older readers fail closed before restore while the signature binds the emitted envelope bytes.

use gaze::{
    CodecAuditRow, CodecCapabilitySet, DocumentExtension, ExtractionDensityPolicy, Scope,
    Session, TextOrigin,
};

let session = Session::new(Scope::Conversation("doc-1".to_string()))?;
let mut codec = CodecAuditRow::new(
    "gaze.codec.pdf",
    "0.7.0",
    "application/pdf",
    TextOrigin::Hybrid,
);
codec.advertised = CodecCapabilitySet::new(true, true, true, false);
codec.delivered = CodecCapabilitySet::new(true, true, false, false);
codec.extraction_density_policy = ExtractionDensityPolicy::Required(1.0);
let extension = DocumentExtension::builder(1)
    .clean_md_sha256([1; 32])
    .layout_json_sha256([2; 32])
    .report_json_sha256([3; 32])
    .page_count(4)
    .audit_session_id(session.audit_session_id())
    .codec_audit(vec![codec])
    .build()?;

let manifest_bin = session.export_with_extension(extension)?.into_bytes();

Failure modes match Session::export: ephemeral sessions return Error::ExportForbidden, JSON encoding failures return Error::SnapshotDecode, and empty integrity-binding hashes or audit session ids return Error::EmptyDocumentIntegrity. page_count == 0 is allowed because text-only or degenerate bundles may have no pages while still binding file hashes. Callers must treat the returned SensitiveSnapshot as owner-only because it carries the full token-to-PII restore map. Bundle layout details live in docs/explanation/document/document-extension.md.

Source

pub fn import(snapshot: SensitiveSnapshot) -> Result<Self>

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more