Skip to main content

h2hdb

h2hdb is the database and coordination core for the H2HDB multi-repository system. It owns the SQLite/MariaDB schema, bounded transactional workflows, and backend-neutral application facades. Komga and OPDS use the catalog facade; ingest uses the transaction-owning ingest facade and downloader uses the queue facade.

It deliberately does not scan files, parse galleryinfo.txt, manipulate images, choose filesystem paths, serve HTTP, serialize OPDS documents, or depend on hbrowser. Those responsibilities belong to consumer adapters and sibling packages.

Greenfield BCNF schema

The current schema is a clean epoch-2 design:

  • 306 catalog data-plane base relations checked as BCNF, plus 73 generated logical views for read-oriented projections. Thirty-eight generic sealed vertical families keep ordinary bases at their semantic key plus at most one value.
  • 29 declared decompositions, each checked as lossless and dependency-preserving.
  • 75 operational control-plane base relations checked as BCNF, one of which is the separately created epoch-control relation, plus one derived activation view. The complete epoch therefore contains 306 + 75 = 381 base tables and 74 logical views, or 455 schema objects in those two manifests.
  • One generated physical schema for SQLite and MariaDB, with backend-specific SQL rendered from the same closed-world manifests.
  • A separate physical-width gate requires each ordinary catalog_* base table to have its semantic primary key plus at most one atomic non-key value. It reports 294 narrow bases and 12 exact approved-wide BCNF recompositions: the seven gallery-linear current-state rows for observation metadata, scan, directory, stat, gallery manifest, analysis owner candidate, catalog publication storage, plus gallery identity, artifact semantic input, prepared artifact, catalog artifact occurrence, and artifact blob with its mandatory locator. Logical views are excluded from that policy.
  • The closed catalog physical-domain authority contains exactly 272 relations: 218 mutation relations and 54 read-only views. The complete publication graph is inside that closure, including permanent finalization replay state.
  • The generated provider installs exactly 4,913 typed bootstrap rows per backend, including all 18 fixed 256-shard cleanup ranges.

The logical sources of truth are verification/schema/catalog.toml and verification/schema/operational.toml. They declare functional dependencies, keys, decompositions, bootstrap facts, and semantic obligations. Deterministic generators derive the physical manifests, Lean schema proofs, and the wheel-resident runtime provider. Generated SQL is not a second schema-authoring surface.

This is a greenfield cutover. There is no v1-v7 upgrade or adoption path, no legacy-epoch compatibility layer, and no dual write. Read-only logical views inside epoch 2 are deliberate read models. A previous or foreign database must be replaced with an empty database and rebuilt from source data.

The numbered migration runner, monolithic H2HDB facade, and their hand-written legacy repositories are not shipped. In particular, the old catalog_build_discoveries relation is not part of the package or generated schema. Production SQL relation names are checked against the two physical manifests in both source and built-wheel verification, so a second hand-written catalog_* or operational_* schema cannot silently bypass the manifest audit.

Schema epoch

The active identity is epoch=2, schema_version=1. The singleton h2hdb_schema_epoch row binds the exact generated DDL, bootstrap-seed, and semantic-obligation manifests into one durable checksum.

Initialization follows a fail-closed state machine:

  1. A truly empty database is admitted and recorded as BUILDING.
  2. Idempotent generated DDL and bootstrap rows are applied in deterministic slices.
  3. The complete object set, bootstrap facts, and activation obligations are validated.
  4. The exact manifest is atomically marked READY.

If construction is interrupted, rerunning migrate may resume only the same checksum-matching BUILDING epoch. A READY rerun validates the exact current epoch. Drift, an unknown control residue, or any other non-empty database is rejected without adoption or destructive repair.

The command name migrate is retained as the administration interface, but it constructs or resumes this single manifest-bound greenfield epoch; it does not run numbered historical migrations.

Core responsibilities

  • MariaDB and SQLite connectors, read/write transactions, and connector-enforced read-only access.
  • Manifest-bound schema construction, full validation, and O(1) readiness checks.
  • Normalized catalog identities, retained revision/commit audit descriptors, publication preparation, and current-head-only catalog reads.
  • Durable download-to-ingest handoff, exact attempt/lease fencing, and coordinated completion.
  • Bounded source-build, analysis, publication, cleanup, maintenance-gate, canonical-value, event, and hash-cache workflows.

Cross-table workflows use one connector and one managed transaction. Mutable authority is represented by normalized heads, generations, leases, seals, and receipts rather than by caller-supplied counts, cursors, digests, names, or tokens. Long-running work advances through bounded, replayable batches; pointer publication validates sealed scalar state in a short transaction.

Installation

This repository uses a src layout and an independent uv environment. It is not part of a uv workspace, and uv.lock is intentionally ignored.

uv venv --python 3.14
uv pip install -e ".[dev]"

Rebuild the local environment after toolchain changes with:

./scripts/rebuild-env.sh

Configuration

{
  "database": {
    "sql_type": "sqlite",
    "database": "/var/lib/h2hdb/catalog.sqlite3",
    "access_mode": "read-write"
  },
  "maintenance": {
    "optimize_enabled": true
  },
  "logger": {
    "level": "INFO",
    "file": null
  }
}

For MariaDB, also set host, port, user, password, and database. The supported MariaDB baseline is 10.11.11, including Synology's 10.11.11-1551 package build. The integration gate pins the upstream mariadb:10.11.11 image and verifies the server version before creating its test database. Read-only consumers should use "access_mode": "read-only" and a database account limited to the metadata/read privileges required by schema validation and application reads.

JSON string values consisting exactly of ${ENV_NAME} are resolved from the process environment before validation. Variable names must match [A-Za-z_][A-Za-z0-9_]*; missing or invalid variables stop startup without including their values in the error. Inline interpolation such as db-${INSTANCE} is deliberately unsupported, and unknown JSON fields are rejected.

Schema administration

The CLI exposes exactly three operations:

uv run --no-sync python -m h2hdb migrate --config config.json
uv run --no-sync python -m h2hdb check --config config.json
uv run --no-sync python -m h2hdb ready --config config.json

Choose the operation from database state:

Database state or caller Operation
Truly empty database Run migrate to construct epoch 2/version 1
Matching interrupted BUILDING epoch Rerun migrate to resume
Matching READY epoch Run read-only check for the full audit
Consumer startup Run check; never initialize schema
Frequent readiness probe Run the O(1) read-only ready check
Previous, foreign, or drifted schema Create a new empty database and rebuild

The wheel-resident generated provider must resolve every required runtime validator and recurring writer binding before it opens or mutates a database; the public administration API does not accept a substitute provider. check holds a read transaction while validating the complete READY schema; ready validates only the exact epoch/version/manifest marker.

Applications can use the same administration boundary directly:

from h2hdb import VNextDatabaseAdminFacade, load_config

config = load_config("config.json")
admin = VNextDatabaseAdminFacade(config)
admin.initialize()  # deployment init job only
admin.check()  # full read-only audit
admin.check_readiness()  # lightweight probe

Public application API

Consumers should import the public facades and immutable domain values from h2hdb; they must not import connector, repository, generated-schema, or table implementation modules.

Current-head catalog reads use open_database, which performs the full manifest-bound READY audit before returning a VNextCatalogFacade. The returned descriptor fences one call and is accepted only while it still equals the current head; a head advance makes an older descriptor fail closed:

from h2hdb import load_config, open_database

catalog = open_database(load_config("readonly-config.json"))
revision = catalog.get_catalog_revision()
page = catalog.list_publications(
    revision=revision,
    offset=0,
    limit=50,
    require_artifact=True,
)
publication = catalog.get_publication("42", revision=revision)

Download request creation, bounded listing, and exact-request completion use VNextDownloadQueueFacade:

from h2hdb import VNextDownloadQueueFacade, load_config

queue = VNextDownloadQueueFacade(load_config("writer-config.json"))
request = queue.request_download(42, "https://example.invalid/gallery/42")
pending = queue.list_download_requests(limit=100)
queue.complete_download_request(request)

Each facade call owns a fresh connection and one bounded read or write transaction. Repository methods that accept connectors or units of work remain internal coordination surfaces.

VNextIngestFacade.prepare_source() consumes the source adapter once, outside every database transaction, and freezes the exact observation pages in a private disk-backed spool. The manifest preflight and later bounded staging steps therefore read the same immutable bytes even if the live source changes mid-run; closing the prepared-source handle removes the temporary spool.

After complete_ingest() releases its SHARED gate lease, resident integrations call VNextIngestFacade.drain_current_only_maintenance(). Each cleanup transaction selects at most 256 logical cleanup keys/families under a renewable EXCLUSIVE lease; each selected key executes only a schema-fixed bounded set of physical deletes. One public attempt advances at most 16 cleanup batches. The typed result is DONE, PROGRESSED, BLOCKED, or CONTENDED; residents immediately retry PROGRESSED, while blocked/contended attempts use the ordinary poll cadence. Every result retains no caller capability, and durable shard checkpoints make response-loss replay safe. Cleanup retains the prior payload until the new current receipt is fully PROJECTION_FINALIZED and no live publication-candidate or source-build predecessor pins it.

Deliberate current limits

  • A nonblank catalog search query fails closed until a normalized, current-head search index is part of the manifest and reader contract.
  • The durable contract needed to derive CatalogPublication.redownload_required for the current revision is not closed. Readers therefore do not infer it from transient operational rows.
  • Core defines and orchestrates the typed artifact-preparation/storage boundary, but concrete filesystem and object-storage behavior remains in the consumer adapter.

Verification

The schema workflow and implementation checks are:

uv run --no-sync python scripts/verify-formal.py coverage --validate-only
uv run --no-sync python scripts/verify-formal.py schema
uv run --no-sync python scripts/verify-formal.py lean
uv run --no-sync python scripts/verify-schema-surface.py
uv run --no-sync black --check src tests scripts
uv run --no-sync ruff check src tests scripts
uv run --no-sync mypy src tests scripts
uv run --no-sync pytest
uv run --no-sync python -m build

verify-formal.py schema checks manifest validity and generator drift for the physical, Lean, operational-refinement, and runtime-provider artifacts. The Lean target proves the declared closed-world BCNF and decomposition statements under their explicit assumptions; it does not by itself prove SQL/runtime refinement.

coverage --validate-only validates the evidence-index structure while still reporting unresolved production blockers. Plain coverage is the strict production-readiness gate and must remain nonzero until every reported blocker has real evidence. Schema and Lean success must not be presented as strict coverage success.

SQLite tests run locally. Set H2HDB_TEST_MARIADB=1 with a working Docker daemon to include the pinned MariaDB 10.11.11 testcontainer cases.

The distribution boundary can be checked with:

uv run --no-sync python scripts/build-and-verify-distributions.py \
  --output-directory /path/to/empty/output-directory

It builds in a fresh temporary directory, verifies the wheel's closed schema surface and removed-module boundary, and confirms that the installed CLI exposes only migrate, check, and ready.

Local release gate

Install the versioned Git hooks once per clone:

./scripts/install-git-hooks.sh

The installer refuses to disable an existing hooks path or executable legacy hook; compose those hooks explicitly before switching this clone to .githooks. VS Code's built-in Git and command-line Git honor the installed hooks. GitHub web edits and clones where the installer has not run do not.

Ordinary commits and merges do not run the expensive release suite. When the staged project.version in pyproject.toml changes, the pre-commit or pre-merge-commit hook performs only the cheap version and clean-tree policy. For a version-increasing master push, the pre-push hook reuses a matching receipt or automatically runs the complete local release gate against a clean, checked-out HEAD:

  • Black, Ruff, and mypy;
  • coverage-contract and generated-schema drift checks;
  • Lean proofs and the required small TLC profiles;
  • the complete SQLite and MariaDB 10.11.11 test suite; and
  • the installed-distribution boundary check.

The gate requires the development environment, Docker for MariaDB, the Lean toolchain declared by lean-toolchain, and either host Java or Docker for TLC. Deep TLC remains an explicit manual check. A successful gate writes a local, non-versioned receipt under the repository's Git metadata and binds it to the exact committed tree and project version. The push proceeds only when that receipt is valid, so retrying the same commit does not rerun the suite.

To verify an already committed clean HEAD explicitly, or to force a fresh verification, run:

uv run --no-sync python scripts/release-gate.py run
uv run --no-sync python scripts/release-gate.py run --refresh

GitHub-hosted formal verification is manual-only. The PyPI workflow rechecks the version transition, builds and smoke-tests the distributions, and publishes them; the expensive correctness evidence is owned by the local release gate.

Multi-repository development

The repositories remain independent projects. For an isolated editable-install smoke environment, run:

./scripts/rebuild-multirepo-integration.sh

See docs/multi-repo-deployment.md for the database ownership, initialization, and consumer-adapter deployment boundary.

License

GNU Affero General Public License v3 or later. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

h2hdb-0.23.0.11.tar.gz (2.3 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

h2hdb-0.23.0.11-py3-none-any.whl (1.4 MB view details)

Uploaded Python 3

File details

Details for the file h2hdb-0.23.0.11.tar.gz.

File metadata

  • Download URL: h2hdb-0.23.0.11.tar.gz
  • Upload date:
  • Size: 2.3 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for h2hdb-0.23.0.11.tar.gz
Algorithm Hash digest
SHA256 ac77158995f633fe62f27e849a84d297efb12be3b6ade725a7847ba86722a465
MD5 fdd17ead2bcf379402985f3ca5e6a3a3
BLAKE2b-256 4e2791a6363cfe8dcc8add57cbb7ee8ad9015a50a7cb55976c93d3507955e831

See more details on using hashes here.

Provenance

The following attestation bundles were made for h2hdb-0.23.0.11.tar.gz:

Publisher: publish.yml on Kuan-Lun/h2hdb

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file h2hdb-0.23.0.11-py3-none-any.whl.

File metadata

  • Download URL: h2hdb-0.23.0.11-py3-none-any.whl
  • Upload date:
  • Size: 1.4 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for h2hdb-0.23.0.11-py3-none-any.whl
Algorithm Hash digest
SHA256 7110b2df84ce612d4cb62ece53d69b8b64f2b04c626bfa684d0e3b8576bc061d
MD5 3cae9234ffe3ff17cf216056922d0b3f
BLAKE2b-256 a08f52a7fd93aa721ef25eac809e5b503e0573d82e3f6aada878258634ee1c84

See more details on using hashes here.

Provenance

The following attestation bundles were made for h2hdb-0.23.0.11-py3-none-any.whl:

Publisher: publish.yml on Kuan-Lun/h2hdb

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.31.0

2 files

0.29.0

2 files

0.28.3

2 files

0.28.2

2 files

0.28.1

2 files

0.27.0

2 files

0.26.0

2 files

0.25.0

2 files

0.24.0

2 files

0.23.1

2 files

This release

0.23.0.11 This release

2 files

0.23.0.10

2 files

0.23.0.9

2 files

0.23.0.8

2 files

0.23.0.7

2 files

0.23.0.6

2 files

0.23.0.5

2 files

0.23.0.4

2 files

0.23.0.3

2 files

0.23.0.2

2 files

0.23.0.1

2 files

0.22.0.2

2 files

0.22.0.1

2 files

0.21.0.1

2 files

0.21.0.0

2 files

0.20.0.2

2 files

0.20.0.1

2 files

0.20.0.0

2 files

0.19.0.0

2 files

0.18.0.0

2 files

0.17.0.0

2 files

0.16.0.0

2 files

0.15.0.0

2 files

0.14.0.2

2 files

0.14.0.1

2 files

0.14.0.0

2 files

0.13.0.2

2 files

0.13.0.1

2 files

0.13.0.0

2 files

0.12.0.6

2 files

0.12.0.5

2 files

0.12.0.4

2 files

0.12.0.3

2 files

0.12.0.2

2 files

0.12.0.1

2 files

0.12.0.0

2 files

0.11.0.5

2 files

0.11.0.4

2 files

0.11.0.3

2 files

0.11.0.2

2 files

0.11.0.1

2 files

0.11.0.0

2 files

0.10.8.4

2 files

0.10.8.3

2 files

0.10.8.2

2 files

0.10.8.1

2 files

0.10.8.0

2 files

0.10.7.5

2 files

0.10.7.4

2 files

0.10.7.3

2 files

0.10.7.2

2 files

0.10.7.1

2 files

0.10.7.0

2 files

0.10.6.0

2 files

0.10.5.8

2 files

0.10.5.7

2 files

0.10.5.6

2 files

0.10.5.5

2 files

0.10.5.4

2 files

0.10.5.3

2 files

0.10.5.2

2 files

0.10.5.1

2 files

0.10.5.0

2 files

0.10.4.2

2 files

0.10.4.1

2 files

0.10.4.0

2 files

0.10.3.0

2 files

0.10.2.0

2 files

0.10.1.1

2 files

0.10.0.0

2 files

0.9.1.9

2 files

0.9.1.8

2 files

0.9.1.7

2 files

0.9.1.6

2 files

0.9.1.5

2 files

0.9.1.4

2 files

0.9.1.3

2 files

0.9.1.2

2 files

0.9.1.1

2 files

0.9.1.0

2 files

0.9.0.5

2 files

0.9.0.4

2 files

0.9.0.3

2 files

0.9.0.2

2 files

0.9.0.1

2 files

0.9.0.0

2 files

0.8.0.2

2 files

0.8.0.1

2 files

0.8.0.0

2 files

0.7.0.9

2 files

0.7.0.8

2 files

0.7.0.5

2 files

0.7.0.4

2 files

0.7.0.3

2 files

0.7.0.2

2 files

0.7.0.1

2 files

0.7.0.0

2 files

0.6.68.63

2 files

0.6.68.62

2 files

0.6.68.61

2 files

0.6.68.60

2 files

0.6.68.59

2 files

0.6.68.58

2 files

0.6.68.57

2 files

0.6.68.56

2 files

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