Skip to content

v0.6.8

Choose a tag to compare

@github-actions github-actions released this 09 Oct 06:13
· 26 commits to main since this release
529e680

⚠️ Upgrading

  • One migration, FoldTaggedModelNames, applied at startup. Data only: it renames a saved framing
    or a measurement whose model is spelled with a :latest tag (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 (UpdatedUtc for 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 on nomic-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__MAXFILEBYTES plus
    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, answers 413 "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 is 202, with the overrun listed under failed with a
    null file, so a script checks failed and does not treat every 202 as a full success.
    failed[].file is null for 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
    without workspacePath is 400: it was read as the workspace root, and an empty string still is
    the root. PUT /api/tokens/{id}/corpora without corpusIds is 400, and an empty list still
    lifts the restriction. POST /api/tokens with an expiresInDays below 0 or above 36,500 is
    400; a negative number made a key that never expired.
  • The skill changes (dexicon-skill-version: 6). Running scripts/install-mcp.ps1 again
    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__MAXFILEBYTES raised 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
    /tmp measured 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's failed array 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.md has an Upgrading section and a configuration
    table keyed by the .env names. 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 after admin 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 in DEXICON_ADMIN_PASSWORD is 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.sh stops 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
    compiles src/ and fails on a use of the request's CancellationToken that 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. Catching OperationCanceledException around the use does not clear
    it. A function or type exempted by a [SuppressMessage] with a non-blank justification is skipped,
    and a MessageId limits 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 a catch reached after a write, and the test
    fails when src/ 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 test job, 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 lists embeddinggemma:latest and a set records embeddinggemma; a saved framing and a
    measurement were stored under the name they were sent with, so the indexer read a different
    row and saving answered reindexing: []. 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 the dexicon:// 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_context already 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 under failed, and with nothing stored
    the answer is 400 "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 with 400 after 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/tokens lists it with an
    empty corpusIds, 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 and null, 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}/corpora without corpusIds threw on the missing list. It answers 400
      and keeps the mapping.
    • POST /api/tokens with expiresInDays of 2147483647 threw in DateTime.AddDays, and a
      negative value made a key that never expires. Values below 0 or above 36,500 days answer 400
      "Invalid expiry".
    • POST /api/corpora/{x}/sources without workspacePath added a source over the workspace root,
      so a misspelt field indexed everything mounted. It answers 400.
    • 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 under failed and stores the files beside it; an attach
      answers 409 "Name already used". Bytes that are not attached to the corpus yet, sent under a
      name it holds, still replace that document.
    • POST /api/search and /api/context, and the MCP search_index tool, with a mode other than
      hybrid, semantic or keyword (in any letter case; an empty one is hybrid) threw an
      ArgumentException that nothing caught. REST answers 400 "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 null among the names in corpus of those two requests threw on splitting it. It answers 400.
    • 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 answered 500, because the name was written unescaped
      into the Location header. Each name is escaped as a path segment.
    • A workspacePath holding a null character threw in Path.GetFullPath when a source or a corpus
      folder was added. It answers 400.
  • 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 in 500 with 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 answered 500 on 0.6.7 and 18 of 24 on main at f691e4b, and none did with this change.
    The probe is not in the repository; ConcurrentStoreOfOneDocumentTests reproduces the failure on
    the old code. The upload that loses now reports the stored file with deduplicated: 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 answers 500.
  • A refused empty file read (Parameter 'content'). The empty-file and blank-file-name refusals of
    an upload passed the argument name to ArgumentException, so failed[].error and 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.mjs signs in with an admin session instead of
    an API key, which never carries admin; scripts/retrieval-bench.py queries files and content
    that exist; scripts/bench/sweep.py signs in as admin; scripts/dev.ps1 password returns the
    password. The unused Markdig package is removed from Dexicon.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.