zettelkasten.sharing.scrub¶
zettelkasten.sharing.scrub ¶
Per-unit id-secrecy scrubbers for Zettelkasten sharing.
Mirrors :func:memory.sharing.bundle._scrub_cross_links: before a unit is
encrypted, any reference it carries to ANOTHER unit that is not visible to ALL
of this unit's recipients is physically dropped, so a recipient can never learn
(via a link/prerequisite/relation/membership) the id of a note/box/citation it
was not itself granted.
Each scrubber takes the unit's raw on-disk bytes plus the KIND-SPECIFIC allowed
sets it needs — the unit-keys of that kind that every recipient of this unit may
also open (the superset-allowed set; see
:func:zettelkasten.sharing.bundle.build_bundle). A reference must be resolved
against the allowed set of the kind it actually points at (a source box's
recipient set must never satisfy a same-named project reference, and vice
versa), so the sets are passed per-kind rather than as one pooled allowed
set. Unit-keys use the same scheme the resolvers produce:
- a note ->
"<box>/<note_id>"(allowed_notes) - a source ->
"<box>"(the box name;allowed_sources) - a project->
"<project_name>" - a citation -> bare
"<id>"(allowed_citations; thecitation:prefix is stripped by the builder before it reaches a scrubber)
Every scrubber is a byte-for-byte NO-OP (returns the input unchanged) when it drops nothing, so a granted unit with no dangling references round-trips exactly. Citations carry no node-id cross-references and therefore need no scrub (encrypted verbatim by the builder).
scrub_note ¶
scrub_note(raw: bytes, *, box: str, allowed_notes: set[str], allowed_citations: set[str], allowed_sources: set[str] | None = None) -> bytes
Drop a note's references to units outside its per-kind allowed sets.
allowed_notes holds the note unit-keys ("<box>/<id>"),
allowed_citations the bare citation ids, and allowed_sources the box
names (_meta unit-keys) that every recipient of this note may also open.
allowed_sources is pooled with allowed_notes only to decide whether a
BOX is safe to name (see :func:_nameable_boxes). Scrubbed fields:
links[]— an edge is dropped WHOLE when it does not resolve into the matching allowed set (see :func:_link_allowed): a_citationsedge is checked againstallowed_citations, every other edge againstallowed_notes(same-box and cross-box).prerequisites[]— a prerequisite (a same-box note id) is dropped when"<box>/<id>"is not inallowed_notes.grounding.dataset_note— a data-citation note ref that may target a SIBLING box viagrounding.dataset_graphOR an embeddedid@graphform (see :func:zettelkasten.data_grounding._load_dataset_note, which partitions the ref on@and letsdataset_graph— else the embedded segment — name the box). The effective box isdataset_graph or <embedded> or boxand the effective ref is the part before@; it is resolved against"<effective_box>/<ref>". When that note is not inallowed_notesBOTHdataset_noteanddataset_graphare dropped (dropping onlydataset_notewould leak the sibling box name viadataset_graph— or via the embedded@graphsegment). When the note IS allowed the ref is kept, but if adataset_graphis set AND thedataset_noteembeds a DIFFERENT box after@(which the loader ignores in favour ofdataset_graph),dataset_noteis rewritten to its bare pre-@ref so that dead, un-checked embedded box name never ships. Othergrounding.*fields are derivation metadata, not node ids, and are left untouched.snapshot— thesnapshotblock on an equation note is a visual-provenance pointer that can name its source box in TWO independent places: the schema-optionalsource_graphlocator AND the LEADING segment ofpath(a committed"<box>/_equations|_snapshots/<id>.png"sidecar from :func:zettelkasten.equation_snapshot._sidecar_path). Both are resolved INDEPENDENTLY (see :func:_snapshot_boxes) rather than gating onsource_graphbeing truthy — otherwise the box would re-leak viapathwhensource_graphis absent, or via apathnaming a different un-shared box than a nameablesource_graph. When EITHER resolved box is not nameable (see :func:_nameable_boxes: no note inallowed_notesand no_metainallowed_sources), the scrub is DENY-BY-DEFAULT: the whole block is rebuilt keeping ONLY the non-locatingpage/rectprovenance (_SNAPSHOT_SAFE_FIELDS), sosource_graph+path+content_hashAND any other (possibly box-bearing) key are dropped together — closing the residual leak where a non-sidecar-shapedpathor an extra box-bearing key bypassed a pop-known-keys scrub. The wholesnapshotblock is left unchanged (byte-identical) when every resolved box is nameable (or it carries no box-identifying field at all).
source.node_id (spine) is a node-id reference that is INTENTIONALLY left
as-is (spine sharing is deferred). data.* storage pointers
(backend/path/content_hash) are NOT box locators and are left
intact, EXCEPT data.fetch.params.path — a producer-local filesystem
locator (host-absolute or workspace-anchored; see
:func:zettelkasten.datasets._fetch_file) that is dropped deny-by-default so
a shared api dataset note never leaks the producer's filesystem layout (a
shareable fetch.params.url is kept). Returns raw unchanged when
nothing is dropped.
Source code in zettelkasten/sharing/scrub.py
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 | |
scrub_source_meta ¶
Drop a source _meta.yaml's relations[] to boxes outside allowed_sources.
A source relation targets another SOURCE box, so a relation survives only
when its target box is in allowed_sources (a bundled source visible
to every recipient of this meta). A same-named PROJECT never satisfies the
reference — only source-kind keys are consulted. Returns raw unchanged
when nothing is dropped.
Source code in zettelkasten/sharing/scrub.py
scrub_project ¶
Scrub a project manifest to its granted sources / cross notes.
sources[]— a source box survives only when it is NAMEABLE (see :func:_nameable_boxes): it has a bundled_metainallowed_sourcesOR a bundled note inallowed_notes. Keying on_metaalone wrongly dropped a box the recipient can legitimately see via a shared note. A same-named project must never satisfy it — only source/note kinds are consulted.cross[]— a_crossnote id survives only when"_cross/<id>"is inallowed_notes(a bundled NOTE).default_spine— blanked unconditionally (spine sharing is deferred, so a shared project never names an organizing spine).
Returns raw unchanged when nothing changes.
Source code in zettelkasten/sharing/scrub.py
scrub_review ¶
scrub_review(raw: bytes, *, allowed_notes: set[str], allowed_sources: set[str], allowed_citations: set[str], allowed_projects: set[str], allowed_orgs: set[str]) -> bytes
Scrub a review manifest (_reviews/<name>.yaml) of un-shared refs.
Manifest scalars are blanked when they name a unit not visible to every
recipient: project (unless in allowed_projects), graph (unless
the box is nameable), and spine (unless the org is in allowed_orgs).
The overlay block — review-wide keys AND the default outline's per-outline
keys — is scrubbed via :func:_scrub_editorial_overlay. The manifest's
default-outline scaffold cache CAN embed corpus note ids (a claim_uid
"<graph>::<note_id>" or a {graph, id} note ref), exactly like an
outline sidecar's scaffold, so it is deep-scrubbed via
:func:_deep_scrub_refs — the same backstop :func:scrub_outline applies.
Returns raw unchanged when nothing is dropped.
Source code in zettelkasten/sharing/scrub.py
scrub_outline ¶
scrub_outline(raw: bytes, *, allowed_notes: set[str], allowed_sources: set[str], allowed_citations: set[str], allowed_orgs: set[str]) -> bytes
Scrub an outline sidecar (_reviews/<name>.outlines.json) of un-shared refs.
The sidecar is {"outlines": {<id>: {id, title, question, spine, overlay,
scaffold}}}. Each record's overlay is scrubbed via the shared
:func:_scrub_editorial_overlay, its spine (an org id) is dropped when
not in allowed_orgs, and its scaffold blob is deep-scrubbed as a
backstop. Returns raw unchanged when nothing is dropped.
Source code in zettelkasten/sharing/scrub.py
scrub_organization ¶
scrub_organization(raw: bytes, *, allowed_notes: set[str], allowed_sources: set[str], allowed_citations: set[str], allowed_reviews: set[str]) -> bytes
Scrub an organization (_organizations/<owner_type>/<owner_name>/<id>.json).
The organization's OWN identity (id, owner) rides the encrypted
placement header — the recipient is granted this org, whose address includes
its owner (mirroring how a note's box is shipped in its header) — so owner
is left intact. Cross-references are scrubbed: row_axis and columns
graph/target-node refs (see :func:_scrub_axis/:func:_scrub_columns);
spine_ref/last_spine_ref (synthesis box names, blanked when not
nameable); review/table_id (blanked when the review is not in
allowed_reviews); and the overlay. Every overlay correction is scrubbed
deny-by-default: member_adds are dropped when they pin a note that is not
bundled — including one with a BLANK graph — or target an un-nameable
row_id box (see :func:_org_member_add_ok); attributions naming an
un-shared note are dropped; and cell_overrides / member_removes (whose
note_id is a bare id revealing no box, but whose row_id CAN encode a
box) are dropped when their row_id box is not nameable (see
:func:_row_id_box_ok). Author cell_overrides summaries and bare
member_removes note ids on a NAMEABLE row are left. Returns raw
unchanged on no-op.
Source code in zettelkasten/sharing/scrub.py
943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 | |
scrub_table ¶
Scrub a review's table grid file (_reviews/<name>.tables.json).
The file is {"tables": {<table_id>: {row_axis, columns, rows, ...}}}. A
table rides its review's recipients verbatim, and scrubbing is superset-gated,
so a recipient who can see NONE of the corpus (a full-corpus review, or a
review:<name> grant widening to a non-viewer) must not receive the grid
SHELL — its un-shared box names, paper-title row labels, and verbatim cell
quotes. Deny-by-default, mirroring :func:scrub_note's rebuild philosophy:
row_axis/columnsgraph refs are scrubbed (see :func:_scrub_axis/:func:_scrub_columns).- Each ROW whose box is not nameable to every recipient is DROPPED WHOLE (not
shipped as a scrubbed shell; see :func:
_row_nameable). - Each surviving row's cells are scrubbed of un-shared members + corpus-derived
evidence/summary, and its transient
materialdigest is dropped (see :func:_scrub_row). attached_note_idsis filtered to ids still routed into a surviving member, so a dropped note id never ships.- A table whose rows are ALL dropped is OMITTED from the file; if that leaves
the file with no tables at all, the whole unit is OMITTED
(:data:
OMIT_UNIT) so an empty/leaky shell is physically never shipped.
Returns raw unchanged when nothing is dropped, or :data:OMIT_UNIT when
the unit must be physically omitted.
Source code in zettelkasten/sharing/scrub.py
1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 | |
note_citation_refs ¶
Citation ids a note references via links[].graph == "_citations".
Used by the builder to resolve citation recipients (a citation rides the union of the recipients of every note that cites it). Returns an empty set for an unparseable note.