pub struct Session { /* private fields */ }Expand description
Owns the token manifest for one conversation or request.
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.
§Restore workflow
- Call
Pipeline::redact- returns agaze_types::CleanDocumentand updates the session map. - Call
Session::exportto produce aSensitiveSnapshot. - Persist the raw bytes (
snapshot.into_bytes()) encrypted at rest - they contain original PII. - Send only the cleaned text to the LLM.
- After the LLM responds, call
Session::importwithSensitiveSnapshot::from(bytes). - For each token in the LLM response, call
Session::restore_strict(orSession::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)Implementations§
Source§impl Session
impl Session
pub fn new(scope: Scope) -> Result<Self>
pub fn from_policy(policy: &Policy) -> Result<Self>
pub fn from_policy_with_ttl_override( policy: &Policy, ttl_secs_override: Option<u64>, ) -> Result<Self>
pub fn tokenize(&self, class: &PiiClass, raw: &str) -> Result<String>
pub fn tokenize_with_family( &self, family: &str, class: &PiiClass, raw: &str, ) -> Result<String>
pub fn format_preserving_fake( &self, class: &PiiClass, raw: &str, ) -> Result<String>
Sourcepub fn tokens(&self) -> Vec<String>
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.
pub fn contains_token(&self, token: &str) -> bool
pub fn session_hex(&self) -> String
pub fn audit_session_id(&self) -> &str
pub fn restore_strict(&self, token: &str) -> Result<String>
pub fn restore(&self, token: &str) -> Option<String>
pub fn export(&self) -> Result<SensitiveSnapshot>
Sourcepub fn export_with_extension(
&self,
extension: DocumentExtension,
) -> Result<SensitiveSnapshot>
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/architecture/document-extension.md.