Repository navigation
v0.6.8
⚠️ Upgrading
- One migration,
FoldTaggedModelNames, applied at startup. Data only: it renames a saved framing
or a measurement whose model is spelled with a:latesttag (in any letter case) or with capital
letters to the lower-case name without the tag, which is the name the indexer reads. Where several
spellings of one model have a row it keeps the newest (UpdatedUtcfor a framing,MeasuredUtc
for a measurement) and drops the others. A chunk set whose framing was saved from the tagged
Models row picks it up on its next pass and re-embeds, which is what saving the framing already
said it would do. A measurement stored under the tagged name now applies to a set that records
the untagged one: the set's chunk budget follows the measured characters per token instead of the
default of 4, so that set re-chunks on its next pass. A 16-file set onnomic-embed-text, which
measures 2.82, went from 217 to 303 chunks. The migration cannot be undone. - A document upload is bounded as a request: ten files at
DEXICON__UPLOAD__MAXFILEBYTESplus
1 MiB of multipart framing, which is 2,098,200,576 bytes at the default. A body that declares a
larger length, or that crosses the bound before any file is stored, answers413"Upload too
large" with a message that names the bound. When files were stored before the bound was
crossed they stay stored and the answer is202, with the overrun listed underfailedwith a
nullfile, so a script checksfailedand does not treat every202as a full success.
failed[].fileisnullfor any failure of the request as a whole and a file's name otherwise.
Only the per-file cap applied before. A script that posts a batch beyond the bound needs to
split it. - Three requests are refused that were accepted or answered
500.POST /api/corpora/{x}/sources
withoutworkspacePathis400: it was read as the workspace root, and an empty string still is
the root.PUT /api/tokens/{id}/corporawithoutcorpusIdsis400, and an empty list still
lifts the restriction.POST /api/tokenswith anexpiresInDaysbelow 0 or above 36,500 is
400; a negative number made a key that never expired. - The skill changes (
dexicon-skill-version: 6). Runningscripts/install-mcp.ps1again
upgrades installed copies. The hooks are unchanged.
Changed
- An upload is streamed into the blob store. The endpoint read the whole multipart form into
/tmp, a tmpfs in the compose file, before the per-file cap applied: a 400 MB file, sent with
DEXICON__UPLOAD__MAXFILEBYTESraised above its default of 209,715,200 bytes, held 400 MiB for
about 8 seconds. Each file now goes from the connection into the store under the cap, and the peak in
/tmpmeasured in the container was 0 for that file, a 50 MB file and a mixed batch. A file
over the cap or an empty one is listed in the response'sfailedarray and the files beside
it are stored. - The OpenAPI documents declare the status each operation returns. Twelve operations answered
201, 202 or 204 while declaring 200, so a generated client typed the wrong response. The
committed documents and the typed web client are regenerated. - The documentation was checked against the code and corrected. Statements that no longer
matched the code were found in the setup, install and upgrade procedures (README.md,
docs/09-deployment.md), the MCP, auth and integration references, the data model and the UI
guide, and were corrected.docs/09-deployment.mdhas an Upgrading section and a configuration
table keyed by the.envnames. The README quickstart was followed step by step on a fresh
clone, and the steps that failed were corrected. - The first-start log and the sign-in screen say how to read the admin password. It is the
quoted value on the third line afteradmin password, so the commands read
grep -A 2 "admin password". The banner no longer says the password can be changed in the UI;
a value set inDEXICON_ADMIN_PASSWORDis applied on every start and nothing else changes it.
The error logged when an embedding provider cannot embed names the provider and model it tried. scripts/backup.shstops Qdrant as well as Dexicon during the backup, restarts what it
stopped even when the archive fails, exits nonzero if that restart fails, and takes the Compose
project name from.env. It had archived an empty volume under a project name set there.- The Documents screen names each file the server refused. A drop of several files showed
only "N files stored", so a file over the size cap or an empty one vanished without a reason.
Each is listed with its reason under the count, and a failure of the whole request is listed
without a file name. - A test rejects the request's token after a catalogue write.
RequestTokenAfterCommitTests
compilessrc/and fails on a use of the request'sCancellationTokenthat a catalogue write can
reach, whether named, copied, linked, captured by a lambda or local function, or read from
HttpContext.RequestAborted, so a job, audit line or reply that follows a save on that token
is found where it is written. CatchingOperationCanceledExceptionaround the use does not clear
it. A function or type exempted by a[SuppressMessage]with a non-blank justification is skipped,
and aMessageIdlimits it to findings with that target. The test's summary lists what it does
not see, among them a token held in a field and acatchreached after a write, and the test
fails whensrc/gains a project it does not read or code under#if(CONTRIBUTING.md). - Stacked doc comments fail CI.
scripts/check-doc-comments.py, a step of the required
Build and testjob, fails on a/// <summary>line whose previous non-blank line is also
///. Inserting a member between another member's doc block and its signature leaves that
shape, and a documentation build does not report it. The script reads every tracked*.cs
file and fails when it reads none. Its one hit, a stale summary stacked on
CodeChunker.IsFenceDelimiter's, is removed.
Fixed
- A framing saved from the tagged Models row never reached a set recorded without the tag.
Ollama listsembeddinggemma:latestand a set recordsembeddinggemma; a saved framing and a
measurement were stored under the name they were sent with, so the indexer read a different
row and saving answeredreindexing: []. Both are now stored and read under the lower-case
name without:latest, saving rebuilds every set on the model whichever spelling it records,
and the rebuilds are not cancelled once the framing is committed. The default for new
corpora badge also requires the default provider. See Upgrading for the migration. - Two sources holding the same file path were read as one text.
GET /api/corpora/{x}/file
and thedexicon://file resource stitched the chunks of both into one text with no warning.
They read the source with the most chunks and say how many sources hold the path, as
get_contextalready did, counting a source whose copy is empty. - A body that ends before its closing boundary answered 500. Files completed before the cut
stay stored and are now indexed, the cause is listed underfailed, and with nothing stored
the answer is400"Malformed multipart upload". - A client that disconnected after documents were attached left them waiting for the next
refresh. The job that indexes an upload, an attach to another corpus, a new corpus created
with a folder, a new chunk set, or a chunk-set change that re-chunks was queued on the request's
token, so a cancel between the save and the job left the saved change with nothing queued. A new
chunk set stayed marked as degraded, and a change was not re-applied by sending it again. The job
is no longer cancellable once the change is saved, and an upload cut off partway keeps and
indexes the files completed before it. The file being attached at the moment of the cut is
discarded, not saved without being reported. - A key issued for chosen corpora could be saved reaching every corpus. The key and its
corpora were two saves: an unknown corpus id was refused with400after the key was saved,
and a cancel between the saves left it unmapped. A key with no mapping reaches every corpus,
and its secret had not been shown. The corpora are checked first and saved with the key. A key
an earlier version already left unmapped is not repaired:GET /api/tokenslists it with an
emptycorpusIds, nobody holds its secret, and it can be revoked. - Requests that answered
500, or were read as something else. Each was found by sending every
JSON-body operation an empty object andnull, by reading the attach path for a taken name, or by
an independent review of the request paths, and each was reproduced against a running build. A
refused request changes nothing.PUT /api/tokens/{id}/corporawithoutcorpusIdsthrew on the missing list. It answers400
and keeps the mapping.POST /api/tokenswithexpiresInDaysof 2147483647 threw inDateTime.AddDays, and a
negative value made a key that never expires. Values below 0 or above 36,500 days answer400
"Invalid expiry".POST /api/corpora/{x}/sourceswithoutworkspacePathadded a source over the workspace root,
so a misspelt field indexed everything mounted. It answers400.- Sending a document whose bytes are already attached to a corpus under the name of another
document in it renamed the first onto a path the source already held, and the save failed on the
unique key. An upload lists the file underfailedand stores the files beside it; an attach
answers409"Name already used". Bytes that are not attached to the corpus yet, sent under a
name it holds, still replace that document. POST /api/searchand/api/context, and the MCPsearch_indextool, with amodeother than
hybrid,semanticorkeyword(in any letter case; an empty one ishybrid) threw an
ArgumentExceptionthat nothing caught. REST answers400"Unknown search mode". The value is
shown on one line and cut at 40 characters, with...after it, because the exception is logged
with its message at the Debug level.- A
nullamong the names incorpusof those two requests threw on splitting it. It answers400. - Creating a chunk set, or queuing a sweep, for a corpus or chunk set whose name holds a letter
outside ASCII saved the change and then answered500, because the name was written unescaped
into theLocationheader. Each name is escaped as a path segment. - A
workspacePathholding a null character threw inPath.GetFullPathwhen a source or a corpus
folder was added. It answers400.
- Attaching the first document to a corpus could leave an empty upload source. The source
was saved before the document, so a cancel between them left a source with no files. It is
saved with the document. - Two documents attached to one corpus at the same moment could create two upload sources, and
one document attached to one corpus twice at the same moment could be attached twice. The
upload source, and the document's existing attachment to the corpus, were looked for and then
added with no lock between, so two requests passed the check together. Nothing failed: the
corpus listed two upload sources, and a document attached under both was chunked, embedded and
returned twice. Attaching is now serialised in the process, as the corpus name and the proposal
decisions are. A catalogue that already holds two upload sources for one corpus is not repaired,
and a second process writing the same catalogue is not covered. - Uploading the same new file twice at the same moment answered 500. Each upload looked for the
file's hash, found none, extracted the text and saved. The second save failed on the blob's primary
key (UNIQUE constraint failed: blobs.Sha256), or its move onto the file the first had stored
failed before that, so the request ended in500with the file in no corpus and no job queued.
In a probe of six rounds of four simultaneous uploads of one file, each to its own corpus, 17 of 24
requests answered500on 0.6.7 and 18 of 24 on main atf691e4b, and none did with this change.
The probe is not in the repository;ConcurrentStoreOfOneDocumentTestsreproduces the failure on
the old code. The upload that loses now reports the stored file withdeduplicated: true, as one
that arrived later does, and its own extraction is dropped. A duplicate key on the save, with the
blob's row found, is treated as the other upload having won, and a failed move onto a file that is
already there as the same bytes being stored; any other failure still answers500. - A refused empty file read
(Parameter 'content'). The empty-file and blank-file-name refusals of
an upload passed the argument name toArgumentException, sofailed[].errorand the Documents
screen that lists it ended in a name the sender did not pass. They read'x.txt' is empty.and
A file name is required. - Stale developer scripts.
scripts/screenshot.mjssigns in with an admin session instead of
an API key, which never carriesadmin;scripts/retrieval-bench.pyqueries files and content
that exist;scripts/bench/sweep.pysigns in as admin;scripts/dev.ps1 passwordreturns the
password. The unused Markdig package is removed fromDexicon.Core.
docker pull ghcr.io/merp4/dexicon:0.6.8
Digest sha256:7f3c4930014698353d81a0400000bc4c47c60e47b5cd044e1097a75e06d35a90. An SBOM and build provenance are attached to the
image in the registry.