Release Notes
v0.5.16
This release bounds API reranker memory and keeps timed-out ONNX work from blocking the event loop.
Fixed
- The API reranker uses inference batches of 8 and disables the CPU memory arena for its reranker session. A real-model regression matched ranking and scores within
rel=1e-5, abs=1e-6for 20, 64, and 100 candidates with inputs longer than the model's 512-token limit. - The reranker materializes scores and extracts candidate text in the blocking worker. A per-service semaphore owns native inference until it finishes, even after the request deadline; at most one rerank runs per API process.
- Each rerank request scores at most 50 Content candidates, even when
top_k_rerankoroffset + limitis larger. Requests keep their existing limits and return up to 100 results; candidates after the first 50 stay in fused search order.meta.retrieval.reranked_countreports the actual count.
Operations
- Added API-container memory alerts at 85% for 5 minutes and 95% for 1 minute, plus growing-swap and OOM alerts. A Loki Docker-event rule reports API container starts and restarts, including initial starts and deployment recreates. A Loki rule routes the existing
memory_pressure_warningevent as a warning. - Offline replay with FastEmbed 0.8.0, ONNX Runtime 1.24.4, and the cached production-matching model measured 4,295.8 MiB idle RSS after five 100-candidate calls with batch 64 and the arena enabled. Batch 8 with the arena disabled measured 331.7 MiB idle RSS and a 660.1 MiB process peak under the same workload.
- The alert rules require a cAdvisor scrape, the existing Loki log-shipping/Ruler path, and continuous Docker Engine event shipping from every host for restart detection. This change does not deploy collectors, load production rules, or test a notification.
v0.5.15
This release updates the Product Version to 0.5.15.
Fixed
- Production deploys now keep the 6 GiB API memory target unless explicitly overridden, export the deployment revision, and check trace-storage permissions before changing containers (#561).
- Searches and document lists stay available while obsolete publication generations are cleaned up (#562).
- A readiness converge run that collides with another run's claim keeps its retry budget and task chain (#566; #564).
- Search returns a retryable publication response during publication windows instead of an internal error (#567; #563).
QA
- Added six synthetic client-workflow checks for upload visibility, delete/restore races, typo and concept retrieval, page evidence, and logical folder metadata (#568; #552).
Moved to v0.5.16
- SeaweedFS originals-bucket storage and backfill (#536), and killed-classification detection (#389).
Confirm the Running Release
Check /health or /openapi.json and confirm the reported Product Version is 0.5.15.
v0.5.14
This release updates the Product Version to 0.5.14 and makes soft delete and
restore wait out a concurrent upload instead of failing at once with an internal
error.
Fixed
- Delete and restore no longer answer
503 internal_errorwhile an upload is publishing (#557). A soft delete or restore that arrives while a sibling upload or bulk publish holds the Client publication fence now waits for that fence, bounded byPUBLICATION_LIFECYCLE_FENCE_WAIT_SECONDS(default 5 seconds), and proceeds once it clears. If the fence is still held at the bound, the request answers the retryable503 publication_busywithRetry-After: 1instead of503 internal_error, and succeeds on retry. Recovery, maintenance, and recovery quiescence races on this path reportpublication_busytoo, an unready publication authority reportspublication_unavailable, and a delete or restore whose change already committed returns that committed outcome rather than a partial result. Fence-correctness guarantees from v0.5.13 are unchanged. See the Document Management API for the lifecycle response contract and configuration for the wait setting. - Console chat names an exhausted provider account instead of a rate limit
(#558). When the AI provider rejects a chat turn because the account is out
of credits (
insufficient_quota,credit_balance_exhausted), the Console now shows "Your provider account is out of credits. Add credits or use a different provider key in Assistant settings." instead of "Too many requests. Wait a few seconds, then send again.", and no longer retries that turn. Genuine rate limits keep the existing wait message and retry behavior.
Confirm the Running Release
Check /health or /openapi.json and confirm the reported Product Version is 0.5.14.
v0.5.13
This release updates the Product Version to 0.5.13 and ships publication
reliability fixes for uploads, search, deletes, and health, plus request-correlated
logging and QA tooling. v0.5.12 was never cut, so this release goes straight from
0.5.11 to 0.5.13.
Fixed
- Unsealed uploads stay invisible to Document lists and search (#551, #534, #472).
New upload data is staged dark (
publication_eligible=false) and lit only after its version Manifest is durable, so an in-flight upload can no longer make lists or search return503. Reads stay fail-closed and reject dark records. See Atomic generation publication for behavior and the stage-dark rollout for required deployment constraints. - Search no longer returns
503from dropped Qdrant connections (#553). The API now builds its Qdrant client with a keep-alive expiry below Qdrant's own idle timeout, so it stops reusing a pooled connection Qdrant has just closed. - Soft delete and restore are no longer refused for about two minutes after an upload (#554). The whole-Client authoritative-manifest verification now runs before the per-upload Client fence is taken, and inside the fence only the activating generation is re-checked.
- Soft delete and restore refresh the publication snapshot before each visibility change (#544). A snapshot that goes stale mid-sweep no longer hides one version or reports a spurious failure.
- Partial multi-version soft delete and restore return a retryable
publication_busyinstead of503(#546). Versions that already committed in the same call stay committed when a later version cannot be moved. - Read errors name a missing publication Manifest (#525).
GET /documents,GET /document/:document_id/text, andPOST /searchnow distinguish Manifest-less reads with the stablepublication_authority_inconsistenterror code, and failed searches log a query fingerprint so identical failures can be grouped without logging the query text. See the shared error and retry contract. - Late upload retries cannot recreate a cleaned publication generation (#524). Durable tombstones refuse late staging, cleanup waits for admitted writers, and read-time Manifest checks still fail closed. Existing orphan records require deterministic cleanup; this fix does not repair them automatically.
- Hard-delete timeouts no longer report a false failure (#522). Large-document Hard Delete verifies a timed-out purge before reporting failure, and the Qdrant memory target is raised to match. See the Document Management API.
/healthprobes cost less (#547). Publication schema diagnostics are cached and the discarded Celery worker ping is removed, preserving the response shape. See the Health API for cache refresh and worker monitoring.
Changed
- Request-correlated logs and optional tracing (#528). Requests carry a correlation id through the structured logs, with opt-in OpenTelemetry tracing and log rotation configured for the production containers.
- Every search writes a durable 30-day debug trace (#541).
/searchrecords the raw and corrected query, the top scored results with document and page, and per-stage timings to a host-backed sink, tagged with the request id and resolved client id. The ordinary application log keeps its redacted line; a singleSEARCH_TRACE_ENABLEDsetting (default on) turns tracing off. See Search debug trace. - Deploys build images before stopping containers (#542). The deploy recipes now build every image first and fail before touching running services, and QA can list failed tasks with a wider poll budget.
- Orphan publication generations are detected and reported (#523). Operators can monitor leftover orphan lane evidence. Detection is report-only; cleanup remains manual. See Orphan generation reconciler for the detection rules, alerting gauge, repair procedure, and log lines.
- QA-only activation-delay knob (#549). A test-only
QA_PUBLICATION_ACTIVATION_DELAY_SECONDSsetting (default0, so production is unchanged) holds publication activation inside the activation window so stranded-owner takeover is observable on fast QA stacks. - QA functional suite is sharded for parallel runs (#550). Release QA runs the 67-case suite across parallel boxes at about 80 minutes wall instead of about 4.9 hours. QA tooling and docs only; no product code.
- QA-only production API key and client are documented (#540). One named
qa-clientand its API key are documented for isolation and release QA runs only, never real users.
Upgrade notes
- Production API memory limit rises from 4 GiB to 6 GiB in this deploy.
- Production API healthchecks run less frequently and allow for startup. See Production container healthcheck.
Known issues
- F1 (v0.5.12 QA finding): hard-delete cleanup can answer
503"publication authority unavailable" under a concurrent publication (issues #545, #543). Fixed in v0.5.14 by #557: a delete or restore that arrives while a sibling upload or bulk publish is still publishing now waits for the publication fence and, past the bound, answers the retryable503 publication_busywithRetry-Afterinstead of503 internal_error, and a delete or restore whose change already committed returns that committed outcome.
Confirm the Running Release
Check /health or /openapi.json and confirm the reported Product Version is 0.5.13.
v0.5.11
This release updates the Product Version to 0.5.11 and ships large-document
publishing, safer spelling, publication resilience, and prod Console hardening.
Fixed
- Hard Delete of a large published document no longer reports a false
failure. The Qdrant client now gets an explicit, configurable request
deadline instead of qdrant-client's 5 s REST default and verifies a timed-out
purge before reporting failure. See the Document Management API
for outcomes and retry guidance, and configuration
for the deadline setting. Release QA's
QA-QUAL-CLEANUP-01returned500on a 1,440-chunk published document while the purge had in fact completed. - Status polls for large documents return promptly (#513). Once a job's
publication is ready,
GET /status/{job_id}proves only the generation's Manifest against its committed evidence (record id, lane counts, and lane digests) instead of re-reading every Content and Fuzzy record with vectors on each poll. A document of about 1,400 chunks no longer makes each poll take about a minute. A Manifest that disagrees with the committed evidence still returns503. See the Status API. - Searches keep serving while the API event loop is stalled (#514). The API
worker's publication lease now renews on a dedicated thread with its own Redis
connection instead of on the event loop, on a fixed cadence, with Redis
stamping the lease from its own clock inside the register and renew scripts.
The safe serving window is measured from before each renewal is sent, so a late
reply can no longer extend serving past the Redis-side lease. When slow reranked
Content searches and a concurrent ingest stall the event loop, the worker keeps
its lease and its pinned snapshots for up to 30 s instead of failing searches
with
503until the upload settles. A loop stalled longer than that still loses its lease, and searches fail closed with503. A stalled API can hold up other publications' activation by up to 30 s. Lease, renewal, and safe-deadline settings are unchanged. This does not shorten the stall or the latency of those searches. - Console lifecycle calls survive a slow publication (#515). Console
lifecycle proxies (Library delete/restore, Status polling) no longer abort at
15 s while the backend publication is still working; the operator's retry no
longer lands on a finished operation and reports a false
404 Not found. - Prod Console binds to loopback and fails closed without a key (#512). The
production Console is defined only in the guarded
docker-compose.console.ymloverlay, binds to127.0.0.1by default, and refuses to start withoutINFOCONNECT_API_KEY. Reach it over an SSH tunnel, or explicitly opt in withCONSOLE_PUBLISH_ADDR=0.0.0.0. - Completed upload status remains readable after convergence (#504). See the Status API for successor verification and retry behavior. Publication sequencing and health behavior are described separately below.
- Superseding uploads no longer strand publication fencing (#506). Upload
acceptance is ordered durably per Document Version. A candidate overtaken by a
newer accepted upload ends explicitly as
superseded; a still-current upload publishes one idempotent replacement generation for the same version. Stale generations are rejected before the global fence can change./healthnow keeps serving-safeFENCINGat HTTP200, returns HTTP503immediately for every serving-unsafe publication state, and exposes paused activation through typedpublishinghealth plusserving_readyandpublishing_readymetrics. - Upload retries after a timed-out Content write complete (#511). BM25 sparse vectors are emitted in token order and stored sorted by index, so a retried embedding batch was rejected as an overwrite of its own immutable generation. Replays now compare sparse vectors by index-to-value entries; a genuinely changed vector is still rejected.
- Failed publication cleanup is re-driven until it completes (#511). The durable cleanup-intent drain no longer expires while it waits behind ingest work, and its first enqueue at worker start retries transient broker errors.
- Large-document classification selection resumes (#511). The selector records its progress after each staged batch. A selection interrupted by the soft time limit after completing at least one batch keeps its staged inputs and resumes from the last completed batch on retry.
- Text-less PDF pages never become empty chunks (#510). A PDF page is skipped only when its selected text is blank: immediately when OCR is disabled, or after OCR also returns no text. Mixed documents still publish their searchable pages, while all-empty documents use the existing zero-node completion path. Empty committed chunk evidence continues to fail closed at read time.
- Publication staging writes are byte-bounded across all four lanes (#509). A 16 MiB application ceiling keeps large-document Fuzzy writes below Qdrant's 32 MiB request cap. Oversized individual points fail before HTTP, while complete-generation ID, count, and digest verification still precedes Manifest admission.
- Content spelling correction keeps low-confidence candidates unchanged
(#505). Common Malay affix variants are no longer rewritten, while known typo
corrections such as
tuntu→tuntutare retained; candidate lookup remains Client-scoped. - Completed ingest status reports search readiness only after convergence (#498). Search readiness is reported once the serving snapshot contains that job's generation or a newer generation of the same Document Version.
- Healthy serialized publication backlogs no longer exhaust finalize retries (#496). Finalize retries continue while committed registry progress advances.
- Hard Delete enqueues discovery-readiness convergence (#494). Cached version totals no longer remain stale after a delete.
- Large-document classification bounds representative selection (#473). Representative selection is bounded to an evenly strided sample of at most 256 chunks.
- Search responses expose the publication view (#469). Successful search
responses expose
meta.publication_viewasreadyorpinned_ready, without internal epochs or operation IDs. - Console Library row identity includes
version_id(#467). Collisions between versions of the same document are prevented, with Library and Status workflow coverage. - Exact immutable publication evidence redelivers idempotently (#451). Redelivery resumes through FENCED, ACTIVATED, and READY; mismatched evidence remains rejected.
- Console readiness and recovery UX uses semantic readiness (#417). Typed proxy failures, persistent recovery notices, and preserved view/input state; Library failures no longer look like an empty corpus.
- Metadata-backfill retries adopt a stranded Manifest generation's original job provenance (#416). Exact evidence can replay and activate without weakening immutability.
Changed
- Journey-based QA is the testing entry point (#435, #436, #441, #442, #444, #449, #452, #454, #457, #460, #464, #467, #468, #470, #474, #477, #481, #491). Established and expanded journey coverage across ingest, rejection, Client boundaries, Console, Discovery, formats, chat, lifecycle, retrieval, configuration/limiters, resilience, quality/performance, and parallel execution/reporting, plus opt-in maintenance journeys and portable admission for resource-constrained Colima hosts.
- QA isolation and Office scripts repaired (#478, #480, #481, #486, #487, #492,
#493, #495, #500). No-mistakes worktrees install frontend dependencies before
linting; client isolation and Office QA scripts fixed; aggregate limiter polling
fixed;
--no-buildruns record image provenance; metadata-evidence API parity fixed; legacy-skill targeting respects stack isolation; registry document-reference validation; deliberate no-retry429probes. - 55 low-signal tests pruned (#508). The PR reports an app coverage change of -0.02 percentage points and no meaningful speed gain.
- Dependency updates (#299, #328, #329, #330, #331, #332, #333, #334, #411, #440, #475).
- Product Version. The Product Version is now
0.5.11.
Upgrade / deploy notes
- Prod Console access changes (#512). The prod Console now binds to
127.0.0.1by default, is defined only in the guardeddocker-compose.console.ymloverlay, refuses to start withoutINFOCONNECT_API_KEY, and is reached over an SSH tunnel or an explicitCONSOLE_PUBLISH_ADDR=0.0.0.0opt-in. - Rebuild worker images on deploy. A bare
just prod-upreuses the existing worker images. Always usejust prod-rebuildorjust prod-up rebuildfor code deploys, and confirm workers run new code (image created time, readiness task registered). - Regenerate the runtime plan for the larger Qdrant budget. The planner now
targets 2 GiB of Qdrant RAM. Re-run
just prod-runtime-planbeforejust prod-upand confirmPROD_QDRANT_MEMORYin.runtime/prod.env. See the runtime planner for host-headroom fallback and RAM-plus-swap semantics.
Known issues
- Typo rank-1 retrieval misses on the large banking corpus (pre-existing, not
a v0.5.11 regression). Release QA's
QA-QUAL-RANKING-01failed 4 of 14 matrix queries, every one atypoquery against the three large banking documents:remitanceandremmitancereturned the Citigroup document at rank 1 with the expected Goldman Sachs document at rank 2, andhandbokandorchesrationdid not return the expected document at all.clean,relevance, andno_resultqueries all passed, so the miss is specific to the typo lane. Reproduced verbatim at v0.5.10; CI cannot see it becausetests/integration/test_issue_108_corpus.pyasserts rank 1 only for thecleanandrelevancekinds. - Global publication MAINTENANCE read outage (present since v0.5.10, root fix E2
planned for v0.5.12). About 6–10 s after a metadata edit completes, cleanup of
the superseded generation takes the whole deployment into publication
MAINTENANCEfor 10–30 s. Every read for every Client returns503("publication authority is not ready"), so the viewer shows an error instead of the edit. The same window follows each backfill republish. - Pre-existing H2/T1/H3 functional non-passes and blocked resilience signal
cases (release QA report):
QA-DSC-UPLOAD-01(H2) Both scope expected HTTP 409, got HTTP 200 — probe uses a stale pre-convergence observation;QA-LFC-LIFECYCLE-01(T1)ReadTimeout— 15 s consumer deadline versus variable publication latency;QA-CON-STATES-01(H3) search loading state not rendered within bound — missed observation, not proof of absence.QA-RES-PUBREAD-01/QA-RES-STRAND-01stay blocked: no activation signal observed inside the bounded window, so stranded-owner takeover remains unqualified. - Default 2 GiB embedding-worker operation needs host headroom (#509).
Release QA pushed the embedding worker to its full 2 GiB container limit while
ingesting the 1,440-chunk d01 document: it peaked at 2,048 MiB with
OOMKilled=falseandRestartCount=0on every sample and every job reachedcompleted, but only while the host kept at least 5,725 MiB available. The earlier no-swap MacOOMKilledat 54% container usage was host/VM memory pressure (~2.0 GiB available), not the cap. Keep roughly 5 GiB of host memory free for default 2 GiB operation. - #505 carries the accepted LINDUNG v4 hit@3 trade-off from 37/52 to 35/52 (0 wins, 2 losses, 50 ties; two-sided sign p=0.5). The requested affix-aware sparse-matching follow-up has not started.
- Under slow reranked search plus concurrent ingest, search p50 rose from 10.0 to 16.4 s (#514). Searches that used to fail with 503 are now served (release load testing): the secondary client went from 67×503 to 0×503 while every remaining 503 lands on the ingesting primary during activation windows. This is the cost of serving, not a latency regression in the search path itself.
- P3-F1 change:
/statusnow proves manifest-only evidence per poll.
Confirm the Running Release
Check /health or /openapi.json and confirm the reported Product Version is 0.5.11.
v0.5.10
This release updates the Product Version to 0.5.10 and ships typed metadata
eligibility plus publication reliability fixes.
Fixed
- Exact page numbers during metadata staging (#405). Publication
classification ingest stages nodes to disk and reloads them through
parse_metadata_json, which wraps JSON numbers inExactJSONNumber. The page coercion did not recognize the wrapper, so every staged chunk'spage_numbersilently degraded to1whilepage_labelsurvived — production showed{page_number: 1, page_label: "14"}on every chunk.page_numberandpage_labelnow resolve from one candidate pool so they cannot disagree, and a genuinely absent page stays absent instead of reporting page 1. - Expired publication outbox dispatches terminate (#404). The delivery
reconciler retried forever once
expires_atpassed while a publication dispatch sat inaccepted. Past expiry it now consults the authority op record: READY means activation already landed, so the outbox is deleted; any other state means the generation can never activate, so the outbox is abandoned with an explicit status. A dispatch still running keeps its retry behaviour. - Stranded zero-worker activations recover (#407, closes #406). A recapture
that captured zero workers left the operation ACTIVATED with an expired
recovery owner, and every control tick then failed with
UNCAPTURED_WORKERuntil an API restart. The tick now detects the lapsed recovery-owner lease on FENCED/ACTIVATED operations and hands them to the same takeover path startup recovery uses, so the operation reaches READY without an operator restart. The release also restores real Redis pipeline error text: redis-py < 7.4 (pinned by kombu < 6.5) replaced it with a literal{exception.args}placeholder, hidingUNCAPTURED_WORKER/PUBLICATION_BUSYin logs.
Changed
- Typed hybrid metadata eligibility (#403). The uniform string gate let
structured values match on shared fragments or dense similarity —
2024-03-15matched2023-03-15lexically andINV-2024-0042matchedINV-2024-0043semantically. One eligibility policy now covers exact, lexical, and semantic candidates in initial retrieval and paged evidence hydration: structured queries and values (complete identifiers, dates, numbers) admit only whole-value equality; mixed queries keep digit-bearing components verbatim; descriptive values keep lexical matching and independent semantic candidates. Structured queries skip the dense embedding and both non-exact lanes, and lane budgets stay separate until eligibility so ineligible high scorers cannot crowd eligible candidates out of bounded windows. Stored vectors, signatures, publication proofs, and Discovery v2 readiness are unchanged: no backfill, no discovery v3. - CI gating (#402). The ~10–13 minute issue-203 acceptance job moved off
the per-PR path to a nightly cron on main plus a
v*tag release gate;frontend-checksnow halts early with success when a PR touches nofrontend/orpackage-lock.jsonpath. - Product Version. The Product Version is now
0.5.10.
Production Verification
Production was deployed to 0898ab3 — the tip of this release — under a
explicitly authorized exception ahead of the tag, and the deploy was verified.
This release is a code-release marker: production keeps reporting Product
Version 0.5.9 until it is redeployed onto the tagged build, which is a
separate authorization. No deploy action is part of this release.
Confirm the Running Release
Check /health or /openapi.json and confirm the reported Product Version is 0.5.10.
v0.5.9
This release updates the Product Version to 0.5.9 and ships publication availability
and reliability fixes plus discovery-readiness hardening.
Fixed
- Reads keep serving through publication finalize (#397). Readers now pin the last
ready snapshot while a publication is fenced or activated, so Soft Delete, Restore,
PATCH /update, and re-embed no longer open an availability window during finalize. Only a request whose Client epoch moves mid-request, or one running while the operation is ACTIVATED, gets503withRetry-After: 1(#387). - Concurrent publication finalization retries (#395). A finalize that loses the race against another publication now retries instead of dropping the publication.
- Metadata readiness auto-converge (#385). The post-ingest convergence task now reliably drives Discovery Readiness to Ready without a manual operator backfill.
- Discovery readiness convergence hardening (#399). A leftover ingest marker from
a crashed upload no longer makes the readiness sweep skip a Client and mask the
stall; the sweep no longer silently stops rescheduling after a delayed run
expires; and the initial
worker_readysweep enqueue now retries.
Changed
- Docs cleanup (#400). Documentation was aligned with current operations, including deploy guidance on worker image rebuilds and a deploy-receipts index.
- Product Version. The Product Version is now
0.5.9.
Upgrade Notes
- Rebuild worker images on deploy. A bare
just prod-upreuses the existing worker images. The Celery revision guard reads each worker'sPROD_DEPLOYMENT_REVISIONlabel, which comes from the checkout — so workers running old images still pass the guard while running old code. On 2026-09-23 production ran the new revision label on Sep-17 worker code and Discovery Readiness stopped converging. Always usejust prod-rebuildorjust prod-up rebuildfor code deploys, and confirm workers run new code (image created time, readiness task registered).
Production Verification
The production verification below covers #385, #395, and #397 only. #399 is not
yet deployed to production and needs just prod-rebuild to take effect.
47c198e (#385) and 2a9ef99 (#397) each fast-forwarded the prod
checkout, relaunched the stack, and passed health, worker-revision, and search checks.
The 2a9ef99 deploy only became fully live after a manual just prod-rebuild on
2026-09-23 09:22–09:26 UTC rebuilt the worker images; a 09:50 UTC production canary
then proved a new upload auto-converges readiness (33→34 in 6 seconds), that
content/metadata/both search find it, and that hard delete removes it. Known
cosmetic gap: readiness versions_total does not decrement after hard delete until
the next ingest. Production reported Product Version 0.5.8 at verification time.
See docs/deploy-receipts/v0.5.8-prod-deploy-2026-09-23.md and
docs/deploy-receipts/v0.5.8-prod-redeploy-397-2026-09-23.md.
Confirm the Running Release
Check /health or /openapi.json and confirm the reported Product Version is 0.5.9.
v0.5.8
This release updates the Product Version to 0.5.8 and ships search-readiness reporting,
page-number evidence, metadata-readiness convergence, and publication startup hardening.
Added
- Search readiness in job status.
/status/{job_id}now reports Discovery Readiness alongside embedding progress, so operators can see when Metadata search becomes eligible without a separate readiness query. - Page numbers in content evidence. Search results and content evidence carry page numbers sourced from v2 metadata, restoring page-level citation for parsed documents.
Changed
- Metadata readiness auto-converge. A post-ingest
converge_discovery_readinesstask runs the bounded metadata backfill automatically, so Discovery Readiness reaches Ready without a manual operator pass once ingest evidence is complete. - Publication startup hardening. Publication snapshot and token handling gained safeguards, and embed status reporting during startup reflects real worker state.
- API memory ceiling and OOM diagnostics. The API container runs with an explicit memory ceiling and malloc-arena cap, with diagnostics that distinguish OOM kills from other startup failures.
- Audit telemetry. Audit events now cover the search and ingest paths added since v0.5.7.
- Product Version. The Product Version is now
0.5.8.
Fixed
- Production startup checks Celery worker revisions from a one-shot Compose container,
using the workers' broker configuration without relying on host resolution of
redis. DNS, connectivity, authentication, authorization, and revision-conflict failures remain startup-blocking and now have distinct diagnostics. just prod-rebuildrunsprod-ui-upbefore smoke checks, so it injects the Console's backend key or fails at that step instead of reporting a successful keyless rebuild.- CI frontend checks no longer flake on the frontend dependency path.
- TESTING_GUIDE now documents the metadata filter contract that search enforces.
These changes have local verification only; they do not claim a production deployment.
Confirm the Running Release
Check /health or /openapi.json and confirm the reported Product Version is 0.5.8.
v0.5.2
This release completes the PRD-190 Metadata Discovery production cutover, adds the slice #203
recoverability controls, and updates the Product Version to 0.5.2.
Added
- Metadata Discovery v2.
POST /searchcan retrieve typed Custom Metadata values through themetadatascope or combine them with Content throughboth. Exact whole-value matches rank ahead of lexical and semantic matches; semantic Metadata matches keep the immutable0.75floor forBAAI/bge-small-en-v1.5. Per-Client Discovery Readiness prevents Metadata requests from returning partial or misleading results before the operator backfill is complete. - Slice #203 recoverability harness. The isolated Redis 7.4.10 and Qdrant integration harness exercises publication recovery, named create/validate/restore backups for all four publication stores, and failure cleanup against the real storage services.
Changed
- Crash-safe publication maintenance. Obsolete-generation cleanup uses durable, idempotent intent records, so interrupted cleanup can resume without duplicating work. Four-store restore runs under a leased, single-writer maintenance fence: concurrent contenders are rejected, serving fails closed while fenced, expired owners can be replaced, and failed restores release the fence for a safe retry.
- Case-insensitive search scope.
POST /searchacceptscontent,metadata, andbothin any letter case. ExistingContent,Metadata, andBothrequests remain valid; downstream processing uses the lowercase canonical values. - Product Version. The Product Version is now
0.5.2.
Confirm the Running Release
Check /health or /openapi.json and confirm the reported Product Version is 0.5.2.
v0.5.1
This release closes pre-merge production QA issues and updates the Product Version to 0.5.1.
Fixed
- Redis maxmemory values generated by the production runtime planner now use the same units as publication startup attestation, preserving deployment-derived headroom.
- The publication Redis ACL now permits the full command set used by publication Lua scripts,
including
PTTL,PEXPIRE, andHLEN, allowing Metadata Backfill claims and coherently-empty authority checks to work through the deployed entrypoint. - Production Compose pins the API publication slot to
api-1and the generated Uvicorn worker count to one, preventing multiple processes from sharing a slot. - Before publication bootstrap,
GET /documentstreats a coherently empty client asnot_startedwhile genuine publication outages remain503.
Confirm the Running Release
Check /health or /openapi.json and confirm the reported Product Version is 0.5.1.
v0.5.0
This release gives operators explicit control over when Chat can use public web results, while keeping the deployment gate authoritative.
Added
- Web-search Chat (#206, #208). Chat can augment its document-grounded answer with bounded public
web results and clickable URL citations. The authenticated backend forwards only the generated
query, enforces URL-safety and deadline bounds, and falls back to a document-backed answer when the
provider is unavailable. The first per-turn control shipped default-off behind
WEB_SEARCH_ENABLED; follow-up #208 aligned it with the console's keyboard focus, touch-target, disabled-contrast, and reduced-motion conventions before the three-mode control replaced it. - Auto, Docs-only, and Web Answer Modes (#214). The operator chooses a mode for each turn. Auto
may offer web search only for a live, time-sensitive question; Docs-only never offers it; Web
requires a public web pass before final synthesis. The selected provider model routes among the
eligible read-only document and web tools, and the bounded loop reserves a final tool-free
synthesis call.
WEB_SEARCH_ENABLEDremains authoritative: gate-off Auto behaves as Docs-only, while an explicit Web turn is rejected instead of silently downgraded. - Focused Chat demonstrations (#217). The Chat guide now pairs Docs-only, Auto, and Web with separate focused demo clips, concise captions, and accessible alternative text.
Changed
- GPT-5.6 Luna default for untouched OpenAI sessions (#216). A Chat session with no stored OpenAI
model or reasoning choice starts on
gpt-5.6-lunawith Medium reasoning. Explicit stored model and reasoning choices remain authoritative, and the curated list still intersects live catalog results when an operator supplies a key. - Product Version. The Product Version is now
0.5.0.
Fixed
- Model allowlist upgrade guidance (#177). The runtime still intentionally rejects unapproved
model identifiers during settings import. Configuration and classification guides now distinguish
the one approved ModernBERT artifact from non-runnable upstream references and explain the startup
impact before an operator changes
.env.
Upgrade Notes
-
Rebuild and restart so the new Product Version and console bundle are active:
just prod-down
just prod-up rebuild -
Restore customized model identifiers before upgrading. Compare the deployment's existing
.envwith these seven approved values:SPACY_MODEL_NAME=en_core_web_sm
DENSE_MODEL_NAME=BAAI/bge-small-en-v1.5
SPARSE_MODEL_NAME=Qdrant/bm25
RERANK_MODEL_NAME=Xenova/ms-marco-MiniLM-L-6-v2
RAPIDOCR_MODEL_NAME=rapidocr-onnxruntime
MODERNBERT_ONNX_MODEL_REPO=onnx-community/ModernBERT-base-nli-ONNX
MODERNBERT_ONNX_MODEL_FILE=onnx/model_quantized.onnxAn unapproved value prevents the API and workers from importing their settings. This fail-closed commercial-use allowlist is intentional; the upgrade does not loosen or bypass it.
-
Existing stored Chat model and reasoning choices remain unchanged. GPT-5.6 Luna and Medium apply only where the operator has not made an explicit choice.
Confirm the Running Release
Check /health or /openapi.json and confirm the reported version is 0.5.0. The console sidebar
footer shows the same value.
v0.4.0
The headline of this release is a rebuilt Chat experience in the operators console.
Added
- Rebuilt console Chat (#186). Chat is now a persistent, multi-provider assistant instead of a single throwaway turn list.
- Redesigned chat UI. A centered conversational column caps both the transcript and the composer, user turns read as right-aligned pills and assistant turns as plain prose, and Send moved inside an auto-growing composer. A conversation rail adds New chat, switching, auto-titling from the first message, rename, delete, and recency grouping; conversations persist in browser-local storage (single-tab correctness; cross-tab is best-effort, last-writer-wins).
- Markdown rendering. Assistant answers render as Markdown — headings, lists, tables, and syntax-highlighted code — instead of raw text.
- Three assistant tools.
list_documentsanswers corpus questions ("how many documents do I have") with an exact count instead of guessing from search hits;search_documentsruns filtered retrieval and reports how many chunks matched;get_document_textpulls one document's text or metadata on demand. - Citations grouped by document. Inline numbered markers with a click-to-open preview card and an on-demand full source list replace the always-expanded Sources block. Document indexes stay stable across a turn.
- Bring-your-own-key across providers. OpenRouter, OpenAI, Anthropic, and Google, with a searchable provider-grouped model picker and error messages that distinguish a bad key, an unsupported model, and a rate limit without leaking provider detail. The key stays in the browser session and is never written to conversation storage.
- Neutral corpus phrasing and a consistent assistant voice. The assistant describes the indexed corpus in neutral terms and answers in one voice across providers and models.
- Embedding provenance and repair (#184). Every embedded point is stamped with a filterable pipeline version plus model, runtime, chunker, and extraction provenance. Client-scoped dry-run and apply tooling backfills legacy points honestly and re-embeds targeted documents from stored chunk text, with the provenance surfaced in the console.
- Cited document chat (#183). The Chat view that #186 rebuilt first shipped here: a stateless BYOK chat route that runs a capped search tool loop against
/searchand streams answers with citations to real document ids and snippets. - Deployed version in the console (#181). The sidebar footer shows the product version reported by
/health, so operators can confirm which release they are on. - LAN access for the console (#171). The console's published port can bind all host interfaces while the backend stays on the internal Compose network. The console still has no app-level auth — it relies on LAN trust or an external auth boundary.
- Private, repeatable console deployment (#167). A non-root standalone Next.js image runs as a loopback-bound production Compose service, resolving the server-only API key from the existing backend mapping.
Fixed
- Disconnecting an OpenRouter key now really removes it (#187). The key was stored under a newer namespaced entry, but a legacy entry was still migrated forward on read and was not cleared on disconnect — so an operator who disconnected could be silently reconnected with that key on the next reload. On a shared workstation that could be someone else's key. Disconnect now clears both entries.
- Hybrid search recall on clean corpora (#170). The dense pre-RRF confidence gate dropped from 0.75 to the calibrated 0.70 (sparse stays at 1.0) and the omitted-
alphapath is locked at 0.5, undoing a regression that returned no results for most queries. Nonsense queries still return nothing. - Document Details panel on long documents (#176). Aggregated entities were rendered once per mentioning chunk with no cap — one annual report produced a 37,446px entity block that pushed the extracted text 41 screens down. Entities are now deduplicated case-insensitively and previewed 12 at a time behind a "+N more" disclosure (nothing is discarded), and reading mode skips layout for off-screen chunks. Measured on the same 2,108-chunk document: entity block 37,446px → 773px, forced layout 197.7ms → 9.5ms.
- Search rail fits above the fold (#175). Console search controls are seated so every control is reachable without scrolling.
- Four WCAG failures in the console (#174). The document drawer no longer re-announces a chunk's full text, entities, and vectors to screen readers on every selection, and the drawer's focus and scroll behavior match what it advertises.
- Search pagination docs and CLI (#172, #178). The
/searchendpoint description, field docs,just search, and the search recipe now state the shipped 200-result retrieval window instead of promising unbounded offsets and totals; the CLI no longer builds requests the API rejects with 422. .envbackups can no longer be staged (#173)..gitignorecovered.envbut not the timestamped.env.bak.*files editors leave behind, which held a full copy ofAPI_KEY_CLIENTS. Nothing leaked.
Maintenance
- Product version bumped to
0.4.0through the supported path (pyproject.toml,uv.lock,openapi.jsonin sync). - CI trimmed to the checks currently in use; the disabled jobs' bodies are intact behind an
if: falseand are documented for re-arming (#188, #165). - Frontend test stability work on the document viewer (#179, #180).
Upgrade Notes
-
Rebuild and restart so the new version and console build are active:
just prod-down
just prod-up rebuild -
Documents embedded before this release carry legacy provenance until backfilled or re-embedded (#184).
-
Existing chat conversations start empty: conversation history is new in this release and is stored per browser.
Confirm the Running Release
Check /health or /openapi.json and confirm the reported version is 0.4.0. The console sidebar footer shows the same value.
v0.3.1
Fixed
- Hybrid search ranking. The effective default
alphais now0.5instead of behaving as0.70, restoring the intended balance between dense semantic and sparse lexical ranking. - Relevance filtering. Clean-corpus floors were recalibrated after a regression made hybrid search return no results for about 13 of 15 queries. The dense floor is now
0.70, down from0.75, while the sparse floor remains1.0. Genuine matches are no longer over-filtered, while gibberish and other queries with no confident match still return no results.
Changed
- Product version. The product version is now
0.3.1(up from0.3.0).
This is a search-regression fix-up release. No other user-facing changes are included.
Upgrade Notes
-
Rebuild or restart the service after updating so the new search defaults and product version are active.
just prod-down
just prod-up rebuild
Confirm the Running Release
Check /health or /openapi.json and confirm the reported version is 0.3.1.
v0.2.0
Added
-
Reversible document deletion.
DELETE /document/{document_id}now performs a soft delete by default. Soft-deleted chunks are hidden from normal search and document listing, but can be restored withPOST /document/{document_id}/restore. -
Hard delete mode. Callers that need physical removal can still pass
mode=hard:curl -X DELETE "http://localhost:8000/document/doc-123?mode=hard" \
-H "X-API-Key: super-secret-key" -
Deleted-document views. Use
include_deleted=trueon search or document listing when building an own-client trash or audit view. This flag does not widen client isolation. -
Version-scoped document operations. Upload metadata can include
version_id, and document delete or restore calls can target a specific version with?version_id=<value>. Omittingversion_idapplies the operation to all live versions of the document for the authenticated client. -
Office file ingestion. The upload pipeline supports PowerPoint and Excel content through native
.pptxand.xlsxparsing. Legacy.pptand.xlsconversion is available when LibreOffice conversion is explicitly enabled. -
Search result highlighting (historical). Earlier releases exposed matched-term spans in chunk-oriented responses; current caller documentation uses the Evidence Result contract.
-
Runtime version visibility.
GET /healthnow returns the Product Version, and/openapi.jsonexposes the same value asinfo.version, making it easier to confirm which release is running after an update or rollback.
Changed
- Release version source of truth. The Product Version is managed from
pyproject.tomland checked withjust version-check, so runtime metadata,/health, OpenAPI, and lock metadata stay aligned. - Production runtime sizing. Production startup generates host-aware runtime settings before launching the API and workers.
- Status tooling.
just statusincludes richer local, Docker, JSON, and live status modes for checking API, Redis, Qdrant, and worker health.
Upgrade Notes
-
A plain
DELETE /document/{document_id}no longer physically removes vectors. Usemode=hardonly when permanent removal is required. -
For significant updates, including this version bump, rebuild production services after pulling the release:
just prod-down
just prod-up rebuild -
After restarting, verify the running service version with:
curl http://localhost:8000/healthThe response should include
"version": "0.2.0".
Confirm the Running Release
After updating, confirm the API is serving v0.2.0 from public runtime surfaces:
curl http://localhost:8000/health
curl http://localhost:8000/openapi.json
The health response should include "version": "0.2.0", and the OpenAPI response should include "info": { "version": "0.2.0" }.