MCPcopy Create free account
hub / github.com/VectifyAI/OpenKB / MutationSnapshot

Class MutationSnapshot

openkb/mutation.py:129–257  ·  view source on GitHub ↗

Snapshot of final KB paths touched by a mutation attempt.

Source from the content-addressed store, hash-verified

127
128@dataclass
129class MutationSnapshot:
130 """Snapshot of final KB paths touched by a mutation attempt."""
131
132 kb_dir: Path
133 backup_dir: Path
134 journal_path: Path
135 operation: str
136 details: dict = field(default_factory=dict)
137 entries: dict[Path, Path | None] = field(default_factory=dict)
138 attempts: int = 0
139 # Dirs whose backup was hardlinked (in-process only; not persisted, so a
140 # crash-rebuilt snapshot leaves this empty and rollback falls back to the
141 # safe full-copy path). Drives O(touched) rollback via inode-diff restore.
142 hardlinked_dirs: set[Path] = field(default_factory=set)
143
144 def _journal_data(self, status: str) -> dict:
145 return {
146 "version": 1,
147 "operation": self.operation,
148 "status": status,
149 "kb_dir": str(self.kb_dir),
150 "backup_dir": str(self.backup_dir),
151 "details": self.details,
152 "attempts": self.attempts,
153 "entries": [
154 {
155 "target": str(target),
156 "backup": str(backup) if backup is not None else None,
157 }
158 for target, backup in self.entries.items()
159 ],
160 }
161
162 def write_journal(self, status: str) -> None:
163 self.journal_path.parent.mkdir(parents=True, exist_ok=True)
164 atomic_write_json(self.journal_path, self._journal_data(status))
165
166 def mark_committed(self) -> None:
167 """Mark the journal committed without removing the backup.
168
169 Call this the instant the mutation is durably applied (e.g. the
170 registry write has landed) so a subsequent
171 :func:`recover_pending_journals` discards the journal instead of
172 rolling it back. This is the commit signal; :meth:`discard` is the
173 post-commit cleanup that also removes the backup dir and journal
174 file and must itself be best-effort — it runs *after* the commit
175 point and its failure must never trigger a rollback.
176 """
177 self.write_journal("committed")
178
179 def track_new(self, paths: list[Path]) -> None:
180 """Register paths created *after* the snapshot for removal on rollback.
181
182 Some artifacts get their final name only once the mutation runs — the
183 PageIndex ``{doc_id}`` blob under ``.openkb/files`` is named by indexing,
184 which happens after :func:`snapshot_paths`. Rather than eagerly
185 snapshotting the whole append-only blob store up front (an ``os.link``
186 per existing blob on *every* add — O(total blobs), not O(this doc)),

Callers 2

snapshot_pathsFunction · 0.85
_snapshot_from_journalFunction · 0.85

Calls

no outgoing calls

Tested by

no test coverage detected