Skip to content

dikw-core v0.6.1

Choose a tag to compare

@github-actions github-actions released this 23 Jun 13:48
· 21 commits to main since this release
40bc459

0.6.1 — official GHCR image; client/server version handshake; SQLite + write-path concurrency fixes

Added

  • Official container image published to GHCR on every release. The release
    workflow's new publish-image job builds examples/docker/Dockerfile (the
    same image the Trivy PR-scan builds) and pushes a public, multi-arch
    (linux/amd64 + linux/arm64) image to
    ghcr.io/opendikw/dikw-core:X.Y.Z after the PyPI publish (it waits for the
    wheel to be installable, then pip installs it). There is intentionally no
    floating :latest
    — downstream pins an exact X.Y.Z so its debug
    environment stays reproducible. examples/docker/docker-compose.yml now pulls
    this image by default (pinned via the required DIKW_VERSION env var, with the
    build: block retained as a local-source fallback), giving downstream systems
    a stable, ready-to-run dikw-core to develop and debug their HTTP / dikw client integration against. See examples/docker/README.md and
    docs/deployment-docker.md.
  • Client/server version handshake. dikw client now probes GET /v1/info
    once per invocation and compares the server's engine_version to the client's
    own installed dikw-core version. A confirmed mismatch hard-fails the
    command (exit 1, a version skew: line naming both versions) so a downstream
    system catches silent wire drift immediately — dikw-core is alpha, so a skewed
    client/server pair can misbehave in subtle ways. Ambiguous cases (server
    unreachable, /v1/info non-200, engine_version missing, or the client run
    from an uninstalled source checkout) skip the check and let the real request
    surface its own error, so the handshake never raises a false skew. Set
    DIKW_ALLOW_VERSION_SKEW=1 to downgrade the hard-fail to a one-line stderr
    warning for deliberate mixed-version debugging. The probe is layering-clean
    (reads its own version via importlib.metadata, never imports the engine).
  • GitHub Release is now cut automatically on every tag. release.yml gains a
    github-release job (needs: publish) that creates the GitHub Release for the
    pushed tag once the PyPI publish succeeds: the body is the tag's CHANGELOG.md
    section (extracted from its ## X.Y.Z heading to the next ## ; falls back to
    GitHub's auto-generated notes with a ::warning:: when no section matches), and
    the built wheel + sdist ride along as downloadable assets. Previously the
    workflow only published to PyPI + GHCR and opened the Dockerfile-bump PR —
    Releases were created by hand, and several tags (v0.6.0 and earlier) shipped
    without one. A backfilled v0.6.0 GitHub Release was created out-of-band.

Fixed

  • Concurrency: serialize the SQLite adapter and the synth/lint-apply write
    path.
    Three race conditions could surface under concurrent task execution
    on a single base. (1) Each SQLiteStorage instance shares one
    sqlite3.Connection across the asyncio.to_thread workers a verb fans out
    (retrieval already runs its fts/vec/asset legs via asyncio.create_task),
    and that connection's Python-level state is not thread-safe — overlapping
    workers could trip sqlite3.InterfaceError / phantom rows (the same hazard
    already worked around three times in storage/base.py, eval/runner.py, and
    server/tasks/store_sqlite.py). Every adapter method body now runs under a
    per-instance threading.RLock (acquired inside the worker thread), closing
    the window for every call site rather than one ad-hoc gather at a time.
    (2) synth and lint apply previously took no lock, so they could
    interleave with ingest/delete/wisdom write/each other — racing the same
    deterministic doc_id rows + on-disk page with no enclosing transaction
    (silent anchor loss, embed-version drift, or a healthy page mistakenly
    deactivated). Both now acquire the server's existing base write lock
    (ServerRuntime.ingest_lock), which already covered ingest/import/wisdom/
    delete, so the whole D/K/W write surface is single-writer per base within a
    process. (3) The SQLite adapter now sets an explicit busy_timeout=30000
    (and opens with connect(timeout=30) so the budget also covers the
    connection-time pragmas) — parity with the task store — so a cross-connection
    WAL writer blocks-then-succeeds instead of immediately raising database is locked. No on-disk format, schema, Storage Protocol, or CLI change
    these are internal serialization fixes. ingest_lock stays per-process, so
    multi-replica Postgres deployments are unchanged (cross-replica doc-level
    locking is tracked separately). See docs/server.md § Storage concurrency.

Docs

  • Install-from-PyPI path for downstream consumers. docs/getting-started.md
    §1 now splits installation into Option A (uv pip install 'dikw-core[...]'
    the published wheel, for systems that use dikw-core) and Option B (git clone
    • uv sync — for contributors), and adds an optional-extras matrix
      documenting all three user-facing extras (postgres, cjk, otel): what each
      pulls in, when to install it, and how the feature degrades without it. The
      README install section gains a matching "Install from PyPI" block. Previously
      both entry points led only with the from-source checkout flow, leaving the
      pip-install consumer path (and the cjk extra entirely) undocumented.