Skip to main content

holdings — the placement catalog

A disposable, regenerable catalog answering: which media hold which files? — and the two questions that follow from it: is this backed up? and is it safe to wipe this?

Single file, stdlib only, Python 3.10+. SQLite for placement truth; semantics belong to OntoDAG (see Projection contract, below).

pip install holdings

holdings add-medium laptop-x1      --kind laptop --site home
holdings add-medium drive-budapest --kind drive --durability independent \
                                   --site budapest --location "safe, Budapest"
holdings scan laptop-x1 /home/you
holdings scan drive-budapest /media/you/backup-drive

holdings whereis holiday.jpg                  # which media hold it
holdings redundancy --min-copies 2 --exit-code    # the policy, as a check
holdings redundancy --on laptop-x1 --min-copies 1 # safe to reformat?
holdings due                                  # what to re-read next

Documentation

User Guide how to use it, in the order you will want it
Reference every command, flag, class and column — pinned against the code by the test suite
Publishing putting a catalog where other devices can query it
Roadmap what is done, what is not, and why

Design contract (the important part)

  1. Observer, not authority. holdings only reads filesystems and backup listings. It never writes to your data, never sits in the backup or sync path. Deleting holdings and its database costs nothing but convenience.
  2. Everything is regenerable by re-scanning. The catalog is a cache of facts about the world. The only original data in the whole system is your human OntoDAG categorization — which holdings never touches.
  3. Content hash is identity. sha256:… is the primary key everywhere. Paths, media, snapshots, categories: all attributes of a hash.
  4. Single writer, many readers. Scan on the backup-node laptop; every other device only reads. How the catalog reaches them is not part of the contract — put the SQLite file in a synced folder (Syncthing, or anything else) and every device carries the full index of everything, including drives offline in another country. Or publish it read-only and have them query it without holding it at all (see Reading a published catalog, below). Both work; neither is required.

What it is careful about

The design is mostly a list of wrong answers it refuses to give.

A sync mirror is not a backup. A file on your laptop and in Dropbox has two copies and no backup: one rm removes both. Every medium declares what its copies survive — working, independent, mirror, leased, hosted — and only the last three count. See the User Guide §2.

Seen is not verified. A rescan reuses the stored hash without opening the file, and bit rot changes neither size nor mtime, so a copy can be catalogued faithfully for years and be gone. Instances record how they are known, and --verified-within discounts evidence nobody has refreshed.

Two drives in one drawer are not two sites. --min-sites and --min-kinds make the 2 and the 1 of 3-2-1 checkable, not just the 3.

A copy that expires is not a copy that does not. A lease is counted while it holds and reported when it lapses; hosting nobody else participates in — a Radicle repo seeded only by your own node — does not count at all.

An unreadable file is not a deleted one. A failing drive and a tidied-up one must not produce the same catalog change, so unreadable paths are kept and reported rather than pruned.

Beyond mounted media

Anything that can list itself can be recorded: restic, rclone, S3, a sha256sum run over ssh, a Syncthing cluster, a git remote (GitHub, Hugging Face, Radicle), a Swarm root. Where the source can supply a SHA-256, placement is exact rather than matched by name and size. See User Guide §7.

The catalog can also be published read-only and queried from a phone without holding it — a lookup fetches tens of kilobytes of a catalog that may be hundreds of megabytes — or served offline from a checkout with python web/serve.py. See Publishing.

Projection contract (OntoDAG integration)

(2026-08-20: this contract's canonical statement now lives at the meet point — ontodag docs/plans/PROJECTIONS.md — which generalizes it across sources (files here, messages in ucomm) and adds retention classes. The rules below remain the agreed file-side instance and the wire format is unchanged.)

holdings project-ontodag --out placement.jsonl

Emits JSON lines: {"item": "<hash>", "supercategories": ["sys:on:<medium>", "sys:type:<ext>", "sys:backup:<n>"]} — matching OntoDAG's put(item, supercategories) model.

The agreed rules for the ingesting side:

  • everything under the sys: namespace is machine-written, regenerable cache — never hand-edit, never treat as authoritative;
  • ingestion is an idempotent full rebuild: drop all sys: memberships, re-ingest the stream (no incremental diffing — staleness is the only permitted failure mode, drift is not);
  • the projection is rebuilt locally on each device from the synced SQLite, not synced itself; only the human layer of the DAG is persisted and synced (it is the irreplaceable original data — back it up like the KeePass vault);
  • human categories are attached to the same hash-identified items, giving unified queries like get(photo, vienna, sys:on:drive-budapest).

See ontodag_ingest.py for an adaptation template.

Notes & limits

  • A listing proves paths and sizes, never bytes. Entries are matched to known content when that is unambiguous, recorded as unverified: otherwise, and taken exactly when the source supplies a SHA-256. Mounting the source and scanning it is the only thing that proves content.
  • Symlinks are skipped. Hidden config and caches are excluded by default (.cache, .config, .git, node_modules, Syncthing internals, …); add your own with --exclude-file.
  • Concurrent writes are not supported, by design: single writer, many readers.
  • diff A B is proportional to what is on A, unlike the other reports.
  • holdings cannot verify a durability class or a site. Those are your assertions when you register a medium; what it can do is stop one word standing in for five different things.
  • Tests: pip install -e ".[test]" && pytest — 241 tests, stdlib only, no node and no network; a guard fails if that number drifts from the suite. The browser viewer's logic has its own: node --test web/test/view.test.mjs.
  • Roadmap: see ROADMAP.md.

Metadata

Release files for holdings 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for holdings 0.2.0
File Size Uploaded Provenance Actions
holdings-0.2.0.tar.gz 68.8 kB With provenance Details for holdings-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for holdings 0.2.0
File Interpreter ABI Platform Actions
holdings-0.2.0-py3-none-any.whl Python 3 none any Details for holdings-0.2.0-py3-none-any.whl

Total release size: 107.0 kB

Release files / holdings-0.2.0.tar.gz

Download URL holdings-0.2.0.tar.gz
Size 68.8 kB
Tags Source
SHA-256 checksum
How to use checksums
481091844fb7ae99447d52cec8a483b269613e440e12eb76b90f214a903ae71b
BLAKE2b-256 checksum
How to use checksums
29d31701b3c327040da5e95efe0645d3a025e75ab0c78dc9f0e2cd7ad409ba82
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.

Transparency log

Release files / holdings-0.2.0-py3-none-any.whl

Download URL holdings-0.2.0-py3-none-any.whl
Size 38.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
05caf1e9c95412e12198f809dd462a2202d930d4115b07935bd542d7cc2f0afd
BLAKE2b-256 checksum
How to use checksums
cde6216919477ba5cf09529ee107c4f6f6287be5ba8c8eee16d684626de42e55
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.

Transparency log

Security for holdings 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for holdings-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page