v0.9.7 — Release 0.9.7
⛔ 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/.
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 promoteunder
RECALL_ENV=productionused 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-promotionis 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 ingeneration_ingestproduced 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 rollbacknever 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 underdevelopmentand
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_backnow records
the target's resolved calibration status and an optionalprovisional_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
takesserving_environment, defaulting toenvironment. The wizard is why: it builds every
corpus underdevelopment, because a production build demands a verifiable embedder identity a
bundled model does not have, then serves those tenants withRECALL_ENV=production. Keyed on the
build environment, the gate ran on no tenant an install creates. -
One definition of a pipeline identity.
generation_ingestassembled 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
fastembedextra is bounded to<1.embedder_artifact_pathwalks a private attribute
chain to find a model's own snapshot directory, and does so defensively — a release that renames
it returnsNonerather 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 whilepromoteheld the tenant and generation rowsFOR UPDATE, so a
concurrentforget()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 plainSELECTdoes not wait onFOR UPDATEunder 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: tagrecall-wizard:0.9.1, Dockerfilerecall-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 sogccan 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.