Skip to content

v0.9.7 — Release 0.9.7

Choose a tag to compare

@github-actions github-actions released this 21 Aug 09:55
· 172 commits to master since this release
4764003

⛔ The Windows installer asset has been removed from this release

It could not start. It aborted with

QWidget: Must construct a QApplication before a QWidget

before drawing its window, on every machine. Use
v0.9.8, which fixes it, or the terminal
install at https://giulioder.github.io/RE-call/.

⚠️ The section immediately below was written when this release was published and is kept as it
stood. It describes the file's missing signature, which was a real and separate problem, and it
is no longer the reason the installer is unavailable here.


⛔ The Windows installer in this release is UNSIGNED and will not run

recall-install-0.9.7-windows-x64-UNSIGNED.zip is a bootstrap build. On Windows 11 with Smart App
Control enabled, which is the default on machines that qualify for it, Windows refuses it outright:

An Application Control policy has blocked this file

That is a refusal, not a warning: there is no "run anyway" button. A signature from a certificate
authority in the Trusted Root Program is what fixes it, and this project does not have a certificate
yet. It is published unsigned only because SignPath Foundation's terms require that a project "must
already be released in the form that should be signed" before it can apply, so the first Windows
release cannot be signed by construction.

Do not switch Smart App Control off to run it. It can only be re-enabled by resetting Windows.

If you want recall today, use the Python package this release also publishes:

pip install "recall-rag[mcp,fastembed]==0.9.7"
recall wizard

That is the same engine. recall wizard asks the same questions in a terminal and writes the same
configuration file the graphical installer would; recall wizard --gui opens the window itself if
you install the desktop extra.


Added

  • Production promotion works, gated on certification. generation promote under
    RECALL_ENV=production used to refuse outright with "unavailable in production until
    certification gates land". Those gates have landed: promotion now succeeds for a generation whose
    published calibration certified and is still bound to this pipeline and corpus, and refuses every
    other status by name (MISSING, DRAFT, UNCERTIFIED, STALE each need a different action from
    whoever hit it). --unsafe-development-promotion is refused there rather than ignored, so the
    development escape hatch cannot be carried into production by habit.

  • A desktop upload into a production tenant can reach CERTIFIED. The gate above had no
    reachable door on that path: nothing in generation_ingest produced a calibration, so every
    upload ended built-and-validated but never live, with the CLI as the only route to a live corpus.
    It now calibrates and publishes before promoting, using the same query-set generator the
    installer uses. Measured end to end against a production tenant: a ten-file corpus certifies and
    goes live; a one-file corpus reports "no certifiable query set could be generated from 1
    chunk(s)" and stays ready — the corpus's own reason, not the gate's generic one.

Changed

  • generation rollback never refuses on certification grounds, and this reverses a gate that
    shipped for one release.
    Rollback is the incident path, and a gate that blocks recovery
    precisely when recovery is needed trades a visible degradation for an invisible workaround. Two
    ways the refusal bit, both certain rather than hypothetical: forget() rewrites the corpus
    fingerprint of every generation of a tenant, so one erasure request left no rollback target ever
    again; and every generation an existing install is serving was promoted under development and
    has no published calibration, so upgrading would have removed rollback from all of them.

    The invariant is kept by REPORTING instead of preventing. generation_rolled_back now records
    the target's resolved calibration status and an optional provisional_reason, so a recovery that
    downgrades a tenant from certified to provisional is visible. Reasoning:
    docs/UNCALIBRATED_FIRST_RUN_DESIGN.md.

  • The certification gate follows the SERVING environment, not the build one. GenerationManager
    takes serving_environment, defaulting to environment. The wizard is why: it builds every
    corpus under development, because a production build demands a verifiable embedder identity a
    bundled model does not have, then serves those tenants with RECALL_ENV=production. Keyed on the
    build environment, the gate ran on no tenant an install creates.

  • One definition of a pipeline identity. generation_ingest assembled its own, hardcoding
    provider="fastembed" for every embedder and spelling out a chunker identity with an empty
    configuration. A generation built by the wizard and one built by a desktop upload therefore
    carried different pipeline fingerprints for the same pipeline, which is what makes a published
    calibration resolve STALE. Chunk boundaries are unchanged; only the recorded identity moves.

  • The fastembed extra is bounded to <1. embedder_artifact_path walks a private attribute
    chain to find a model's own snapshot directory, and does so defensively — a release that renames
    it returns None rather than raising, which makes the pipeline identity unverified, which makes
    production builds refuse. An install that silently stops accepting uploads, with nothing pointing
    at the dependency that moved. Verified reachable on fastembed 0.8.0.

Fixed

  • The certification gate decided from a different transaction than the one it authorised. It
    opened its own connection while promote held the tenant and generation rows FOR UPDATE, so a
    concurrent forget() could invalidate the calibration between the verdict and the commit, and the
    connection was acquired while holding a lock. Measured: it is not a deadlock, which is what it
    looks like — a plain SELECT does not wait on FOR UPDATE under MVCC. Now resolved on the
    caller's transaction; a competing fingerprint rewrite waits for the promotion rather than racing
    it.

  • The artifact digest cache vouched for bytes that had changed. Keyed by path with no
    invalidation, the first answer stood for the life of the process, so a re-download or a swapped
    model file kept a provenance claim no bytes on disk supported. Now keyed by file count, total size
    and newest mtime alongside the digest. This detects staleness, not tampering: someone able to
    write into the model directory can also set mtimes.

  • A generated stack could serve one version while every file on disk claimed another. Adding a
    project inherits the existing stack's image tag so the new corpus runs the same recall as its
    siblings, but the Dockerfile was regenerated at the running version. Measured on a 0.9.1 stack
    under a 0.9.6 wizard: tag recall-wizard:0.9.1, Dockerfile recall-rag==0.9.6. Compose reuses a
    tag rather than building it, so the container would have started 0.9.1 in silence. The Dockerfile
    now follows the tag.

  • A refused upload no longer loses the previous upload's files or leaks its corpus. A new build
    seeded its manifest from the ACTIVE generation, and a refused promotion never advances that
    pointer, so each upload dropped every earlier un-promoted upload's files. Measured against a real
    database: upload #2 reported 1 file where it should have reported 3. Builds it supersedes are now
    abandoned so gc can reclaim them.

  • Six operator documents claimed production promotion was blocked. Each was true when written
    and falsified by the gates landing, and each was the document somebody reads before deciding what
    a command will do: PRODUCTION.md, MIGRATIONS.md, CALIBRATION.md, GENERATIONS.md,
    FIRST_CALIBRATION.md, ENVIRONMENT.md.