1use async_trait::async_trait;
10use chrono::{DateTime, Datelike, Utc};
11use serde::{Deserialize, Serialize};
12use std::collections::{BTreeMap, HashMap};
13use std::sync::Arc;
14
15use super::document::OkfDocument;
16use super::record::{from_document, to_document};
17use super::store::{MemoryStore, OkfStore};
18use crate::core::{
19 CanonicalMemory, CanonicalPredicate, CommitReceipt, FactFingerprint, MemoryError, MemoryId,
20 MemoryKind, MemoryStatus, UserId,
21};
22
23pub const MANIFEST_SCHEMA_VERSION: u32 = 1;
25
26#[derive(Debug, Clone, PartialEq)]
28pub enum MemoryWrite {
29 Put(Box<CanonicalMemory>),
31 Delete(MemoryId),
33}
34
35#[derive(Debug, Clone)]
41pub struct MemoryTransaction {
42 pub user_id: UserId,
44 pub expected_revision: Option<u64>,
46 pub writes: Vec<MemoryWrite>,
48 pub idempotency_key: String,
50}
51
52impl MemoryTransaction {
53 pub fn new(user_id: UserId, idempotency_key: impl Into<String>) -> Self {
55 Self {
56 user_id,
57 expected_revision: None,
58 writes: Vec::new(),
59 idempotency_key: idempotency_key.into(),
60 }
61 }
62
63 pub fn expecting(mut self, revision: u64) -> Self {
65 self.expected_revision = Some(revision);
66 self
67 }
68
69 pub fn put(mut self, memory: CanonicalMemory) -> Self {
71 self.writes.push(MemoryWrite::Put(Box::new(memory)));
72 self
73 }
74
75 pub fn delete(mut self, id: MemoryId) -> Self {
77 self.writes.push(MemoryWrite::Delete(id));
78 self
79 }
80
81 pub fn is_empty(&self) -> bool {
83 self.writes.is_empty()
84 }
85}
86
87#[derive(Debug, Clone, Default)]
89pub struct ReconciliationSelector {
90 pub fingerprint: Option<FactFingerprint>,
92 pub subject_predicate: Option<String>,
94 pub subject: Option<String>,
96 pub predicate: Option<CanonicalPredicate>,
98 pub kinds: Vec<MemoryKind>,
100 pub statuses: Vec<MemoryStatus>,
102 pub limit: usize,
104}
105
106impl ReconciliationSelector {
107 pub fn by_fingerprint(fingerprint: FactFingerprint) -> Self {
109 Self {
110 fingerprint: Some(fingerprint),
111 limit: 8,
112 ..Default::default()
113 }
114 }
115
116 pub fn by_subject_predicate(prefix: impl Into<String>) -> Self {
118 Self {
119 subject_predicate: Some(prefix.into()),
120 limit: 20,
121 ..Default::default()
122 }
123 }
124
125 pub fn by_subject(subject: impl Into<String>) -> Self {
131 Self {
132 subject: Some(subject.into()),
133 limit: 40,
134 ..Default::default()
135 }
136 }
137
138 fn matches(&self, memory: &CanonicalMemory) -> bool {
139 let status_ok = if self.statuses.is_empty() {
140 memory.status == MemoryStatus::Active
141 } else {
142 self.statuses.contains(&memory.status)
143 };
144 if !status_ok {
145 return false;
146 }
147 if !self.kinds.is_empty() && !self.kinds.contains(&memory.kind) {
148 return false;
149 }
150 if let Some(fp) = &self.fingerprint
151 && &memory.fingerprint() != fp
152 {
153 return false;
154 }
155 if let Some(prefix) = &self.subject_predicate
156 && memory.fingerprint().subject_predicate() != prefix
157 {
158 return false;
159 }
160 if let Some(subject) = &self.subject
161 && memory.fingerprint().subject() != subject.as_str()
162 {
163 return false;
164 }
165 if let Some(predicate) = &self.predicate
166 && &memory.predicate != predicate
167 {
168 return false;
169 }
170 true
171 }
172}
173
174#[async_trait]
176pub trait MemoryRepository: Send + Sync {
177 async fn get(
179 &self,
180 user_id: &UserId,
181 memory_id: &MemoryId,
182 ) -> Result<Option<CanonicalMemory>, MemoryError>;
183
184 async fn find_candidates(
186 &self,
187 user_id: &UserId,
188 selector: &ReconciliationSelector,
189 ) -> Result<Vec<CanonicalMemory>, MemoryError>;
190
191 async fn all(&self, user_id: &UserId) -> Result<Vec<CanonicalMemory>, MemoryError>;
193
194 async fn revision(&self, user_id: &UserId) -> Result<u64, MemoryError>;
196
197 async fn commit(&self, transaction: MemoryTransaction) -> Result<CommitReceipt, MemoryError>;
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
203pub struct ManifestEntry {
204 pub id: MemoryId,
206 pub path: String,
208 pub status: MemoryStatus,
210 pub kind: MemoryKind,
212 pub fingerprint: String,
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
221pub struct Tombstone {
222 pub id: MemoryId,
224 pub deleted_at: DateTime<Utc>,
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
230pub struct MemoryManifest {
231 pub schema_version: u32,
233 pub revision: u64,
235 pub records: Vec<ManifestEntry>,
237 pub tombstones: Vec<Tombstone>,
239 #[serde(default)]
251 pub applied: Vec<String>,
252}
253
254const APPLIED_KEY_HISTORY: usize = 256;
259
260impl Default for MemoryManifest {
261 fn default() -> Self {
262 Self {
263 schema_version: MANIFEST_SCHEMA_VERSION,
264 revision: 0,
265 records: Vec::new(),
266 tombstones: Vec::new(),
267 applied: Vec::new(),
268 }
269 }
270}
271
272#[derive(Debug, Default)]
274struct Namespace {
275 loaded: bool,
276 revision: u64,
277 records: BTreeMap<MemoryId, CanonicalMemory>,
278 tombstones: Vec<Tombstone>,
279 applied: Vec<String>,
280 written_files: BTreeMap<String, String>,
281}
282
283pub struct OkfRepository<S: OkfStore> {
285 store: Arc<S>,
286 namespaces: tokio::sync::Mutex<HashMap<UserId, Namespace>>,
287}
288
289impl OkfRepository<MemoryStore> {
290 pub fn in_memory() -> Self {
292 Self::new(Arc::new(MemoryStore::new()))
293 }
294}
295
296impl<S: OkfStore> OkfRepository<S> {
297 pub fn new(store: Arc<S>) -> Self {
299 Self {
300 store,
301 namespaces: tokio::sync::Mutex::new(HashMap::new()),
302 }
303 }
304
305 pub fn store(&self) -> &Arc<S> {
307 &self.store
308 }
309
310 fn user_prefix(user_id: &UserId) -> String {
312 format!("users/{}/", user_id.as_str())
313 }
314
315 fn manifest_path(user_id: &UserId) -> String {
316 format!("{}manifest.json", Self::user_prefix(user_id))
317 }
318
319 async fn ensure_loaded(
320 &self,
321 namespaces: &mut HashMap<UserId, Namespace>,
322 user_id: &UserId,
323 ) -> Result<(), MemoryError> {
324 let entry = namespaces.entry(user_id.clone()).or_default();
325 if entry.loaded {
326 return Ok(());
327 }
328
329 let prefix = Self::user_prefix(user_id);
330 let manifest_path = Self::manifest_path(user_id);
331 let manifest: MemoryManifest = match self.store.read(&manifest_path).await? {
332 Some(raw) => serde_json::from_str(&raw).map_err(|e| MemoryError::MalformedRecord {
333 path: manifest_path.clone(),
334 message: e.to_string(),
335 })?,
336 None => MemoryManifest::default(),
337 };
338
339 let mut records = BTreeMap::new();
340 let mut written_files = BTreeMap::new();
341 for path in self.store.list(&prefix).await? {
342 if !path.ends_with(".md") {
343 continue;
344 }
345 let Some(contents) = self.store.read(&path).await? else {
346 continue;
347 };
348 for doc in OkfDocument::parse_many(&contents, &path)? {
349 let memory = from_document(&doc, &path)?;
350 records.insert(memory.id.clone(), memory);
351 }
352 written_files.insert(path, contents);
353 }
354
355 *entry = Namespace {
356 loaded: true,
357 revision: manifest.revision,
358 records,
359 tombstones: manifest.tombstones,
360 applied: manifest.applied,
361 written_files,
362 };
363 Ok(())
364 }
365
366 fn materialize(namespace: &Namespace) -> Result<BTreeMap<String, String>, MemoryError> {
368 let mut grouped: BTreeMap<String, Vec<OkfDocument>> = BTreeMap::new();
369 for memory in namespace.records.values() {
370 let path = category_path(memory);
371 grouped.entry(path).or_default().push(to_document(memory));
372 }
373 let mut out = BTreeMap::new();
374 for (path, docs) in grouped {
375 out.insert(path.clone(), OkfDocument::render_many(&docs, &path)?);
376 }
377 Ok(out)
378 }
379}
380
381pub fn category_path(memory: &CanonicalMemory) -> String {
383 let base = format!("users/{}/", memory.owner.as_str());
384 match memory.status {
385 MemoryStatus::Superseded | MemoryStatus::Expired => format!(
386 "{base}superseded/{:04}-{:02}.md",
387 memory.temporal.updated_at.year(),
388 memory.temporal.updated_at.month()
389 ),
390 MemoryStatus::Staged => format!("{base}staged/patterns.md"),
391 MemoryStatus::Deleted => format!("{base}tombstones/records.md"),
392 MemoryStatus::Active => match memory.kind {
393 MemoryKind::Identity => format!("{base}profile.md"),
394 MemoryKind::Preference | MemoryKind::LocationPreference => {
395 format!("{base}preferences.md")
396 }
397 MemoryKind::Relationship | MemoryKind::RelationshipPreference => {
398 format!("{base}relationships.md")
399 }
400 MemoryKind::Routine => format!("{base}routines.md"),
401 MemoryKind::Commitment => format!("{base}commitments.md"),
402 MemoryKind::CommunicationStyle => format!("{base}communication.md"),
403 MemoryKind::Project => format!("{base}projects.md"),
404 MemoryKind::StagedPattern => format!("{base}staged/patterns.md"),
405 MemoryKind::Episodic => format!(
406 "{base}episodes/{}.md",
407 memory.temporal.valid_from.format("%Y-%m-%d")
408 ),
409 },
410 }
411}
412
413#[async_trait]
414impl<S: OkfStore> MemoryRepository for OkfRepository<S> {
415 async fn get(
416 &self,
417 user_id: &UserId,
418 memory_id: &MemoryId,
419 ) -> Result<Option<CanonicalMemory>, MemoryError> {
420 let mut namespaces = self.namespaces.lock().await;
421 self.ensure_loaded(&mut namespaces, user_id).await?;
422 Ok(namespaces
423 .get(user_id)
424 .and_then(|ns| ns.records.get(memory_id))
425 .cloned())
426 }
427
428 async fn find_candidates(
429 &self,
430 user_id: &UserId,
431 selector: &ReconciliationSelector,
432 ) -> Result<Vec<CanonicalMemory>, MemoryError> {
433 let mut namespaces = self.namespaces.lock().await;
434 self.ensure_loaded(&mut namespaces, user_id).await?;
435 let namespace = namespaces.get(user_id).expect("namespace just loaded");
436 let limit = if selector.limit == 0 {
437 usize::MAX
438 } else {
439 selector.limit
440 };
441 Ok(namespace
442 .records
443 .values()
444 .filter(|m| selector.matches(m))
445 .take(limit)
446 .cloned()
447 .collect())
448 }
449
450 async fn all(&self, user_id: &UserId) -> Result<Vec<CanonicalMemory>, MemoryError> {
451 let mut namespaces = self.namespaces.lock().await;
452 self.ensure_loaded(&mut namespaces, user_id).await?;
453 Ok(namespaces
454 .get(user_id)
455 .map(|ns| ns.records.values().cloned().collect())
456 .unwrap_or_default())
457 }
458
459 async fn revision(&self, user_id: &UserId) -> Result<u64, MemoryError> {
460 let mut namespaces = self.namespaces.lock().await;
461 self.ensure_loaded(&mut namespaces, user_id).await?;
462 Ok(namespaces.get(user_id).map(|ns| ns.revision).unwrap_or(0))
463 }
464
465 async fn commit(&self, transaction: MemoryTransaction) -> Result<CommitReceipt, MemoryError> {
466 let mut namespaces = self.namespaces.lock().await;
467 self.ensure_loaded(&mut namespaces, &transaction.user_id)
468 .await?;
469 let namespace = namespaces
470 .get_mut(&transaction.user_id)
471 .expect("namespace just loaded");
472
473 if namespace.applied.contains(&transaction.idempotency_key) {
476 return Ok(CommitReceipt {
477 revision: namespace.revision,
478 written: Vec::new(),
479 deleted: Vec::new(),
480 });
481 }
482 if let Some(expected) = transaction.expected_revision
483 && expected != namespace.revision
484 {
485 return Err(MemoryError::RevisionConflict {
486 expected,
487 actual: namespace.revision,
488 });
489 }
490 if transaction.is_empty() {
491 namespace.applied.push(transaction.idempotency_key);
492 return Ok(CommitReceipt {
493 revision: namespace.revision,
494 written: Vec::new(),
495 deleted: Vec::new(),
496 });
497 }
498
499 let mut written = Vec::new();
500 let mut deleted = Vec::new();
501 let now = Utc::now();
502 for write in &transaction.writes {
503 match write {
504 MemoryWrite::Put(memory) => {
505 if memory.owner != transaction.user_id {
506 return Err(MemoryError::PolicyRefused(format!(
507 "record {} belongs to {}, not {}",
508 memory.id, memory.owner, transaction.user_id
509 )));
510 }
511 namespace
512 .records
513 .insert(memory.id.clone(), (**memory).clone());
514 written.push(memory.id.clone());
515 }
516 MemoryWrite::Delete(id) => {
517 if namespace.records.remove(id).is_some() {
518 namespace.tombstones.push(Tombstone {
519 id: id.clone(),
520 deleted_at: now,
521 });
522 deleted.push(id.clone());
523 }
524 }
525 }
526 }
527
528 let desired = Self::materialize(namespace)?;
529 for (path, contents) in &desired {
530 if namespace.written_files.get(path) != Some(contents) {
531 self.store.write(path, contents).await?;
532 }
533 }
534 for path in namespace.written_files.keys() {
535 if !desired.contains_key(path) {
536 self.store.remove(path).await?;
537 }
538 }
539
540 let next_revision = namespace.revision + 1;
546 let mut applied = namespace.applied.clone();
550 applied.push(transaction.idempotency_key.clone());
551 if applied.len() > APPLIED_KEY_HISTORY {
552 applied.drain(..applied.len() - APPLIED_KEY_HISTORY);
553 }
554 let manifest = MemoryManifest {
555 schema_version: MANIFEST_SCHEMA_VERSION,
556 revision: next_revision,
557 records: namespace
558 .records
559 .values()
560 .map(|m| ManifestEntry {
561 id: m.id.clone(),
562 path: category_path(m),
563 status: m.status,
564 kind: m.kind,
565 fingerprint: m.fingerprint().to_string(),
566 })
567 .collect(),
568 tombstones: namespace.tombstones.clone(),
569 applied: applied.clone(),
570 };
571 self.store
572 .write(
573 &Self::manifest_path(&transaction.user_id),
574 &serde_json::to_string_pretty(&manifest)?,
575 )
576 .await?;
577
578 namespace.revision = next_revision;
580 namespace.written_files = desired;
581 namespace.applied = applied;
582
583 Ok(CommitReceipt {
584 revision: namespace.revision,
585 written,
586 deleted,
587 })
588 }
589}
590
591#[cfg(test)]
592mod tests {
593 use super::*;
594 use crate::core::{
595 EntityRef, EvidenceCounters, Explicitness, MemorySource, MemoryValue, PrivacyMetadata,
596 RetrievalMetadata, SessionId, TemporalMetadata, TemporalScope, TurnId,
597 };
598
599 fn memory(id: &str, kind: MemoryKind, statement: &str) -> CanonicalMemory {
600 let now = Utc::now();
601 CanonicalMemory {
602 id: MemoryId::new(id),
603 owner: UserId::new("usr_1"),
604 kind,
605 predicate: CanonicalPredicate::new("dietary_identity"),
606 status: MemoryStatus::Active,
607 confidence: 0.9,
608 subject: EntityRef::user(),
609 value: MemoryValue::Text(statement.to_string()),
610 statement: statement.to_string(),
611 evidence_summary: "stated".into(),
612 source: MemorySource::from_explicitness(
613 Explicitness::ExplicitStatement,
614 SessionId::new("ses_1"),
615 TurnId(1),
616 ),
617 temporal: TemporalMetadata::created_at(now),
618 retrieval: RetrievalMetadata {
619 subject: "user".into(),
620 ..Default::default()
621 },
622 evidence: EvidenceCounters::first(),
623 privacy: PrivacyMetadata::default(),
624 temporal_scope: TemporalScope::Persistent,
625 supersedes: Vec::new(),
626 superseded_by: None,
627 qualifier: None,
628 }
629 }
630
631 #[tokio::test]
632 async fn commit_writes_records_and_reloads_them_from_the_store() {
633 let store = Arc::new(MemoryStore::new());
634 let repo = OkfRepository::new(store.clone());
635 let user = UserId::new("usr_1");
636
637 let receipt = repo
638 .commit(MemoryTransaction::new(user.clone(), "tx-1").put(memory(
639 "mem_a",
640 MemoryKind::Preference,
641 "The user is pescatarian.",
642 )))
643 .await
644 .unwrap();
645 assert_eq!(receipt.revision, 1);
646 assert_eq!(receipt.written.len(), 1);
647
648 let reopened = OkfRepository::new(store.clone());
650 let loaded = reopened.all(&user).await.unwrap();
651 assert_eq!(loaded.len(), 1);
652 assert_eq!(loaded[0].statement, "The user is pescatarian.");
653 assert_eq!(reopened.revision(&user).await.unwrap(), 1);
654 }
655
656 #[tokio::test]
657 async fn records_land_in_the_category_file_for_their_kind() {
658 let store = Arc::new(MemoryStore::new());
659 let repo = OkfRepository::new(store.clone());
660 repo.commit(
661 MemoryTransaction::new(UserId::new("usr_1"), "tx-1")
662 .put(memory("mem_a", MemoryKind::Preference, "a"))
663 .put(memory("mem_b", MemoryKind::Relationship, "b")),
664 )
665 .await
666 .unwrap();
667
668 let paths = store.paths();
669 assert!(paths.contains(&"users/usr_1/preferences.md".to_string()));
670 assert!(paths.contains(&"users/usr_1/relationships.md".to_string()));
671 }
672
673 #[tokio::test]
674 async fn a_superseded_record_moves_out_of_its_active_file() {
675 let store = Arc::new(MemoryStore::new());
676 let repo = OkfRepository::new(store.clone());
677 let user = UserId::new("usr_1");
678
679 let mut old = memory("mem_old", MemoryKind::Preference, "The user is vegetarian.");
680 repo.commit(MemoryTransaction::new(user.clone(), "tx-1").put(old.clone()))
681 .await
682 .unwrap();
683
684 old.status = MemoryStatus::Superseded;
685 old.superseded_by = Some(MemoryId::new("mem_new"));
686 let new = memory(
687 "mem_new",
688 MemoryKind::Preference,
689 "The user is pescatarian.",
690 );
691 repo.commit(
692 MemoryTransaction::new(user.clone(), "tx-2")
693 .put(old)
694 .put(new),
695 )
696 .await
697 .unwrap();
698
699 let preferences = store
700 .read("users/usr_1/preferences.md")
701 .await
702 .unwrap()
703 .unwrap();
704 assert!(preferences.contains("pescatarian"));
705 assert!(
706 !preferences.contains("vegetarian"),
707 "superseded record must not linger in the active file"
708 );
709 assert!(store.paths().iter().any(|p| p.contains("superseded/")));
710 }
711
712 #[tokio::test]
713 async fn deleting_a_record_removes_content_and_leaves_a_bare_tombstone() {
714 let store = Arc::new(MemoryStore::new());
715 let repo = OkfRepository::new(store.clone());
716 let user = UserId::new("usr_1");
717
718 repo.commit(MemoryTransaction::new(user.clone(), "tx-1").put(memory(
719 "mem_a",
720 MemoryKind::Preference,
721 "secret preference",
722 )))
723 .await
724 .unwrap();
725 repo.commit(MemoryTransaction::new(user.clone(), "tx-2").delete(MemoryId::new("mem_a")))
726 .await
727 .unwrap();
728
729 assert!(repo.all(&user).await.unwrap().is_empty());
730 for path in store.paths() {
731 let contents = store.read(&path).await.unwrap().unwrap_or_default();
732 assert!(
733 !contents.contains("secret preference"),
734 "deleted content still present in {path}"
735 );
736 }
737 let manifest: MemoryManifest = serde_json::from_str(
738 &store
739 .read("users/usr_1/manifest.json")
740 .await
741 .unwrap()
742 .unwrap(),
743 )
744 .unwrap();
745 assert_eq!(manifest.tombstones.len(), 1);
746 }
747
748 #[tokio::test]
749 async fn a_retried_commit_is_applied_once() {
750 let repo = OkfRepository::in_memory();
751 let user = UserId::new("usr_1");
752 let tx = || {
753 MemoryTransaction::new(user.clone(), "same-key").put(memory(
754 "mem_a",
755 MemoryKind::Preference,
756 "a",
757 ))
758 };
759 let first = repo.commit(tx()).await.unwrap();
760 let second = repo.commit(tx()).await.unwrap();
761 assert_eq!(first.revision, second.revision);
762 assert!(second.written.is_empty());
763 }
764
765 #[tokio::test]
766 async fn a_retried_commit_is_applied_once_across_a_restart() {
767 let store = Arc::new(MemoryStore::new());
772 let user = UserId::new("usr_1");
773 let tx = || {
774 MemoryTransaction::new(user.clone(), "same-key").put(memory(
775 "mem_a",
776 MemoryKind::Preference,
777 "a",
778 ))
779 };
780
781 let first = OkfRepository::new(store.clone())
782 .commit(tx())
783 .await
784 .unwrap();
785
786 let reopened = OkfRepository::new(store.clone());
788 let retried = reopened.commit(tx()).await.unwrap();
789
790 assert_eq!(
791 retried.revision, first.revision,
792 "the retry advanced the revision, so it was applied a second time"
793 );
794 assert!(
795 retried.written.is_empty(),
796 "the retry rewrote records: {:?}",
797 retried.written
798 );
799 }
800
801 #[tokio::test]
802 async fn a_stale_revision_is_refused() {
803 let repo = OkfRepository::in_memory();
804 let user = UserId::new("usr_1");
805 repo.commit(MemoryTransaction::new(user.clone(), "tx-1").put(memory(
806 "mem_a",
807 MemoryKind::Preference,
808 "a",
809 )))
810 .await
811 .unwrap();
812
813 let err = repo
814 .commit(
815 MemoryTransaction::new(user.clone(), "tx-2")
816 .expecting(0)
817 .put(memory("mem_b", MemoryKind::Preference, "b")),
818 )
819 .await
820 .unwrap_err();
821 assert!(matches!(err, MemoryError::RevisionConflict { .. }));
822 }
823
824 #[tokio::test]
825 async fn writing_into_another_users_namespace_is_refused() {
826 let repo = OkfRepository::in_memory();
827 let err = repo
828 .commit(
829 MemoryTransaction::new(UserId::new("usr_other"), "tx-1").put(memory(
830 "mem_a",
831 MemoryKind::Preference,
832 "a",
833 )),
834 )
835 .await
836 .unwrap_err();
837 assert!(matches!(err, MemoryError::PolicyRefused(_)));
838 }
839
840 #[tokio::test]
841 async fn candidates_are_selected_by_subject_and_predicate() {
842 let repo = OkfRepository::in_memory();
843 let user = UserId::new("usr_1");
844 let target = memory("mem_a", MemoryKind::Preference, "vegetarian");
845 let prefix = target.fingerprint().subject_predicate().to_string();
846 repo.commit(MemoryTransaction::new(user.clone(), "tx-1").put(target))
847 .await
848 .unwrap();
849
850 let found = repo
851 .find_candidates(&user, &ReconciliationSelector::by_subject_predicate(prefix))
852 .await
853 .unwrap();
854 assert_eq!(found.len(), 1);
855
856 let none = repo
857 .find_candidates(
858 &user,
859 &ReconciliationSelector::by_subject_predicate("user|something_else"),
860 )
861 .await
862 .unwrap();
863 assert!(none.is_empty());
864 }
865}