Skip to content

Releases: Merp4/dexicon

v0.6.10

Choose a tag to compare

@github-actions github-actions released this 15 Oct 21:41
7c6b9f3

⚠️ Upgrading

  • One migration, KeyReachIsExplicit, adds tokens.AllCorpora. Whether a key reaches every
    corpus is now a setting on the key. The migration sets it for every key that has no corpus rows
    at that moment, which is what those keys reached, and leaves a key that has rows restricted to
    them. A key that the removal of its only corpus had already emptied, in an earlier version, has
    no rows either and becomes a key that reaches every corpus: the catalogue does not record that
    it was restricted. Before upgrading, read GET /api/tokens and look for a key with an empty
    corpusIds that should not reach every corpus; restrict it again with
    PUT /api/tokens/{id}/corpora ({"allCorpora": false, "corpusIds": [...]}) or revoke it.
    Going back to 0.6.9 without restoring the backup taken before the upgrade makes a key
    restricted to nothing reach every corpus again, and upgrading a second time does not repeat the
    backfill (D-42).
  • Key requests that answered 200 or 500 are refused. PUT /api/tokens/{id}/corpora with
    {"corpusIds": []} is 400 (it was a 200 that lifted the restriction), and so is POST /api/tokens with an empty corpusIds and no allCorpora; send allCorpora: true or leave the
    field out. PUT with {} is 400 titled Say which corpora (it was Corpus ids are required). A request that names more than 1,000 distinct corpora is 400 (a very long list was
    a 500). corpusIds is empty both for a key that reaches every corpus and for one restricted
    to nothing, so a client reads allCorpora. An Access page opened before the upgrade sends the
    old request and gets 400 until it is reloaded.
  • Detaching the last attachment of an uploaded document deletes the document
    (D-43): its
    blob row, its extracted text and its bytes under /data/blobs. The same happens when a
    replacement by name, the removal of an upload source or the removal of a corpus takes the last
    reference. Uploading the same bytes again stores and extracts them again. The first start after
    the upgrade also deletes every stored document that no corpus holds (what 0.6.9 and earlier left
    after a detach), and then the files under the blob root that no document names: the bytes of an
    upload whose extraction failed or timed out, which 0.6.9 listed as kept and listed nowhere, the
    temporary file of an upload that a crash cut short, and bytes whose delete failed. Going back to 0.6.9
    does not restore them. Count the documents nothing holds before upgrading:
    SELECT COUNT(*) FROM blobs b WHERE NOT EXISTS (SELECT 1 FROM files f WHERE f.BlobSha256 = b.Sha256).
    Restore the catalogue and /data/blobs together: a catalogue restored alone, older than the
    files beside it, makes the newer files look stray, and the sweep deletes them.
  • Attaching a stored document by its hash needs a corpus the caller reaches that holds it.
    POST /api/corpora/{x}/documents/attach answers 404 No such document otherwise, for a key and
    for an administrator, since a document that no corpus holds no longer exists. A key could attach
    any stored document by its hash before; it now sees only the documents of corpora it reaches.
  • A key that is revoked or has expired, and an administrator session that has signed out, stop
    receiving progress events from the next heartbeat. /healthz counts the corpora a key reaches
    and reports a running job only in one of them (it counted every corpus for any key).

Fixed

  • A key restricted to a corpus reached every corpus once that corpus was removed. Removing a
    corpus deleted the key's rows, and a key with no rows reached every corpus. That held for an
    administrator's delete and for an approved removal, and a key holding propose could then ask
    for the removal of any corpus it could name. It dates from the first scoped keys. A key restricted
    to a removed corpus now reaches nothing. The Access page offers Every corpus and Only
    these
    , and does not read nothing ticked as every corpus.
  • Removing a corpus while a key was being created for it, or while its reach was being
    replaced, answered 500
    on a foreign key. The removal and those two requests now wait for each
    other.
  • Removing a corpus, a source, a chunk set or a document answered 500 when the Qdrant
    collection holding its vectors did not exist
    , as after a lost or replaced Qdrant volume or a
    catalogue restored beside an empty Qdrant. A delete in a missing collection has nothing to do and
    succeeds, and the log says the collection was not there. Any other failure of the vector store
    still stops the removal.
  • A dialog whose title names a file with no space to break at no longer scrolls sideways at phone
    width.
  • The message for an upload whose extraction failed on the server no longer says the bytes stay in
    the blob store. They stay until the next start, which deletes them.

Changed

  • The Documents page asks before a detach and says whether another corpus holds the document. The
    Approvals dialog for a document or a corpus, the confirmation for deleting a corpus and for
    removing the uploaded-documents source, and the destroy scope description say that documents
    no other corpus holds are deleted.
  • The 409 answer for a corpus name that is taken, or that is another corpus's id, no longer repeats
    the stored name of the corpus that holds it. That a name is taken is still told, since names are
    global.
  • The skill is unchanged (dexicon-skill-version: 7).
  • Dependency updates: AngleSharp 1.8.4, radix-ui 1.7.0, lucide-react 1.55.0, and two groups of
    build and test dependencies.
  • The PDF limit in docs/10-security-secrets.md no longer gives the per-platform depths measured
    in probes, and advises indexing and uploading PDFs only from sources you trust.

Known limits

  • An upload of bytes at the moment the last holder of the same bytes detaches them can fail once,
    with a 404 from attach, or with an extraction failure when the bytes were deleted while it read
    them; uploading again works. A blob row stored between the release's check and its byte delete
    keeps its text and loses its bytes, so it searches as any other document and is not extracted
    again after an extractor change.
  • The sweep of /data/blobs assumes one process owns the data folder. A catalogue with no document
    at all leaves hash-named files in place and logs a warning, so a catalogue emptied by detaching
    every document keeps any leaked bytes until a document is stored.
  • The removal of an upload source or a corpus releases its documents one at a time, under the
    attachment lock and, for a corpus, the naming lock. The time for a corpus of thousands of
    uploaded documents was not measured.
  • An upload of bytes that a key already has still answers deduplicated: true and the title of a
    document that another corpus holds, so it tells the key that those bytes are stored there. It
    needs the bytes.

docker pull ghcr.io/merp4/dexicon:0.6.10

Digest sha256:46fb8b753aa2ca6d0fdc15f3f813a10a79478a3a38d2d6826156ef8b52bc81b2. An SBOM and build provenance are attached to the
image in the registry.

v0.6.9

Choose a tag to compare

@github-actions github-actions released this 10 Oct 21:47
4cde778

⚠️ Upgrading

  • No migration, no new setting and no change to docker-compose.yml. Data written by 0.6.9 reads in
    0.6.8, except that a history source that stored /docs fails again there.
  • Uploads are bounded in more ways, and some answers change.
    • A request is read for at most ten file parts and 100 multipart sections, form fields included.
      Past either limit the application reads no more and answers 202 with a request-level failed
      entry (file is null); parts it did not read are in neither stored nor failed, so a script
      reads failed. When none of the first ten files was stored the answer is 400 "No files could be
      stored", or 400 "No files in the request" (its detail repeats the text only when the section
      limit was hit), or 503 when at least one failure was server-side. The transfer is not stopped,
      so no bandwidth is saved.
    • A file name is refused on upload (listed under failed) and on attach (400 "Invalid file
      name") when it holds a control character (line breaks, U+2028 and U+2029 included), a
      bidirectional override or isolate (U+202A to U+202E, U+2066 to U+2069), or is longer than 260
      UTF-16 units. U+200E, U+200F and U+061C pass. Attaching with a blank fileName answers 400;
      leaving it out keeps the name the document was uploaded under, and 400 follows if that name
      breaks the rule, so a script that attaches by hash alone gets 400 for such an older document.
    • DEXICON__INDEXING__EXTRACTIONTIMEOUTSECONDS (300; 0 disables) applies to uploads as well. An
      upload that exceeds it, or fails for a reason that is not a verdict on the file (an I/O error,
      a refused permission, a shortage of memory, a TimeoutException, a library cancellation), is
      listed under failed and no document record is created; the bytes stay in /data/blobs, up to
      DEXICON__UPLOAD__MAXFILEBYTES each, listed nowhere. Sending the file again extracts it again
      for the full budget, with no Retry-After. A concurrent upload of the same bytes is reported as
      stored.
    • A blob_texts row written by 0.6.8 or earlier for one of those failures keeps its message as
      the document's empty reason. It is at the current extractor version, so no pass extracts it
      again, and uploading the same bytes returns it as already stored. Nothing in the product clears
      such a row; a later extractor version extracts it again. GET /api/documents lists the reason
      of each document that has no text.
    • A DOCX, PPTX or EPUB package with an XML part nested deeper than 512 levels, or XML that
      inflates to more than 256 MiB in total, is an unreadable document. An HTML file or EPUB chapter
      with more than 1,000,000 < characters is too. The same limits apply to workspace files: a new
      or changed file over them is an unreadable document, and a file already read is not read again.
      HTML has no nesting limit: its parse is cancelled when the extraction clock runs out, so a
      document too deep to parse within the budget is a timeout, and with a budget of 0 the HTML parse
      is unbounded in time.
  • Creating a corpus checks its chunk settings. A client that posted a size outside 64 to 8,192,
    an overlap that is negative or not below the size, or an unknown boundary mode to
    POST /api/corpora and relied on 201 gets 400, and custom cannot be set at creation. A
    server that sets DEXICON__INDEXING__CHUNKSIZE, CHUNKOVERLAP or BOUNDARYMODE outside those
    rules logs an error at startup and answers 503 to a creation that does not send its own value
    for the setting that is out of range. A size under 64 or over 8,192, an overlap equal to the size
    and a negative overlap used to create corpora that indexed; an unknown mode used to create a
    corpus whose code and text files all failed. docker-compose.yml does not forward BOUNDARYMODE;
    set it in docker-compose.override.yml. A chunk set added from a template inherits the template's
    size and overlap as stored, even when the current rules would refuse them.
  • Glob lists are capped, and git pathspecs are checked at save. A list holds at most 200
    elements and an element at most 500 characters. A history source's include list, and a corpus
    default include list (which history sources inherit), are refused when an element makes git log
    fail on every pass: an empty element, an element holding a null character, malformed pathspec
    magic, a path that climbs out of the repository, a rooted path, or an element starting with /:;
    the rules are those of git 2.54.0 on Linux. The web UI forms and configure_corpus send stored
    lists back, so a stored list that fails a rule blocks any save that sends it until it is edited or
    reset (reset in configure_corpus). That includes a default over the caps and one stored with
    an empty element, //x, /:x, a climbing path, a rooted path or malformed magic, all of which a
    file source read without error. No stored row changes. A history source that stored /docs fails
    again on an older version.
  • Ignore files are read with limits, and a breach fails the source. A walk reads at most 5,000
    rules and 60,000 pattern parts across all ignore files, a file of 1 MiB, and 16 MiB of ignore
    files. A stored list is held to 1,000 rules and 20,000 parts. Passing any of these fails that
    source's walk, for .gitignore and .git/info/exclude as well as .dexiconignore: the source's
    files are not indexed or removed, the corpus reads unavailable and the reason is in
    index_status, naming the files that used most of the budget and, when a .gitignore used some
    of it, offering to turn off useGitignore for the source (in the web UI and over REST; MCP keeps
    it on). A line or list entry over 500 characters fails the walk in a .dexiconignore and in a
    list, and is skipped with a warning in a .gitignore or .git/info/exclude. A stored list with
    an entry over 500 characters, a POSIX class, more than 1,000 rules or a weight over 20,000 makes
    its source unavailable on the first pass after the upgrade, until the list is edited. A
    .dexiconignore that has an unusable line, a carriage return inside a line, a link, a pipe, an
    unreadable file, a NUL, bytes invalid in its encoding or a twin differing only in case fails the
    walk. Before, a link was skipped, a NUL or an invalid byte was read as a character that matched
    nothing, and a lone carriage return ended a line, so a file with old Mac line endings applied and
    now fails the walk; lines end at a line feed, as in git. A .gitignore or .git/info/exclude
    with CR-only line endings is read as one line, matches nothing and raises no warning; one that
    holds a NUL is skipped whole with a warning, where only the NUL line was inert before. Files they
    excluded are indexed on the first pass. Ignore files are found by name without regard to case on
    every platform: on Linux a .GitIgnore or .DexiconIgnore that was ignored before is now read.
    Byte-order-marked UTF-16 and UTF-32 files are still decoded.
  • Bracket classes and some patterns are read as git reads them. A class written with a leading
    !, such as [!a]*.md, matches any character except those listed. No class matches /; a
    positive class that spanned it, such as [+-9], matched it before. A backslash inside a class
    escapes the next character ([\d] is the letter d, not a digit), and [a-z-[b]] is the
    members a-z, -, [ and b followed by a literal ], where it was class subtraction. A
    leading ], a [ and a trailing - are members.
    POSIX classes, any other name between [: and :], empty classes and reversed ranges ([z-a])
    are refused as unusable lines. Three or more stars forming a whole segment are one **. Lines
    are split at line feeds. On the first pass after the
    upgrade, a source whose patterns change meaning indexes the files it now selects and drops the
    ones it no longer does. Differences from git remain and are listed in docs/04-ingestion.md.
  • Removal, attach and promote answer differently. The second of two simultaneous removals of one
    source, chunk set or corpus answers 404 ("removed while this request waited"). A later DELETE
    of a source or a chunk set answers 404, and a later DELETE of a corpus answers 400 from the
    scope resolver. An upload or attach that waited behind the removal of its corpus answers 400 as
    an unknown corpus. A chunk set that became the default or the only set while its vectors were
    being deleted is kept and the removal answers 409; a refresh of the set is queued, and a
    proposal to remove it stays waiting.
  • The embedding model probe needs the admin session. POST /api/embedding-models/probe answers
    403 to a key, whatever its scopes; 0.6.8 accepted a key with search.
  • Log lines and tool replies change. Characters the console template leaves alone are replaced
    with U+FFFD in logged values: DEL, C1 controls, line and paragraph separators, bidirectional and
    other format characters, and a zero-width joiner or non-joiner, so a family emoji or a Persian
    word written with a ZWNJ shows the marker. A line break in a logged value shows as \n inside
    quotes. An exception is written a line at a time: each line is cut at 4,000 characters, the rest
    after the line that crosses 64,000 (about 64,000 in all) and a string property at 8,000, and a
    line the runtime did not write is indented by four spaces. MCP tool errors show a caller's value
    on one line cut at 200 characters, stored text on one line cut at 4,000, only three unknown corpus
    names and a corpus list cut at a whole name within 1,500 characters; the detail of the REST
    400 for an unknown corpus is cut the same way. An agent no longer sees a provider's message or a
    path the workspace resolver refuses; the log has them.
  • The skill changes (dexicon-skill-version: 7). It tells the agent th...
Read more

v0.6.8

Choose a tag to compare

@github-actions github-actions released this 09 Oct 06:13
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/searc...
Read more

v0.6.7

Choose a tag to compare

@github-actions github-actions released this 08 Oct 12:47
2588c89

⚠️ Upgrading

  • One migration, GrantDestroyToIngestKeys, applied at startup. Data only: it appends destroy
    once to every key that holds ingest when it runs, so none of them stops being able to detach
    a document. That includes the key adopted from DEXICON__BOOTSTRAP__TOKEN if its row already
    exists. A token value adopted for the first time after the upgrade is a new key, holds search
    and ingest only, and needs destroy added to detach. The migration cannot be undone, and a
    0.6.6 started afterwards ignores the extra word.
  • DELETE /api/corpora/{id}/documents/{fileId} needs destroy and no longer ingest. A key
    issued from now on is given what is ticked, and destroy is never ticked for a new key, so a
    script that detaches documents with a new key needs destroy added, on the Access page or with
    PUT /api/tokens/{id}/scopes. On a fresh install the key adopted from the environment holds
    search and ingest only.
  • Detaching applies to uploaded documents only. An id from a source that reads a folder or a
    repository's commits answers 404; it used to remove that file's vectors and row while the
    source still listed it.

Added

  • A destroy scope lets a key detach an uploaded document
    (D-40). It is independent
    of ingest, so a key can add documents without being able to remove them, and the other way
    round. The Access page lists it, never ticks it for a new key, and warns that nothing asks
    first. Sources, chunk sets and corpora stay out of every key's reach and are asked for with
    propose.

Changed

  • The 403 for a missing scope says who can grant it. Every scope check over HTTP told the
    caller to sign in to the UI, which is true only of admin. The other scopes now say that
    whoever runs Dexicon can grant them on the Access page, as the MCP tools do.
  • dotnet format and test names are checked in CI. dotnet format Dexicon.slnx --verify-no-changes is a step of the required Build and test job, and 13 files were
    reformatted to pass it. An underscore in the name of a C# test method or class is a build error
    (CA1707). Neither was enforced before (CONTRIBUTING.md).

Fixed

  • The detach endpoint reached files a source had read. It took any file id in the corpus, so a
    key holding the scope it needed (ingest until now) could remove a workspace or history file's
    vectors and catalogue row by giving an id that GET /api/corpora/{id}/files lists to a key that
    can search. Only an uploaded document is detached now, in the cleanup and in
    DocumentService.DetachAsync, which the approved removal of a document uses too.

docker pull ghcr.io/merp4/dexicon:0.6.7

Digest sha256:5d566639a39c1aa87358debce2061b5ad0262a6343eb3566530e6d20204babf1. An SBOM and build provenance are attached to the
image in the registry.

v0.6.6

Choose a tag to compare

@github-actions github-actions released this 08 Oct 03:33
6958ca8

⚠️ Upgrading

  • Two migrations, applied at startup, both additive. FilesSettleBySizeAndModifiedTime adds a
    nullable column to file_chunk_states. AddProposals creates the proposals table. Neither
    changes an existing row.
  • The first pass over each chunk set after upgrading reads every workspace file once and writes
    the settle keys (below), so a corpus with several chunk sets reads its files once per set.
    Uploaded documents and git-history sources are unchanged. Nothing is re-chunked or re-embedded
    for that.
  • A PDF whose cached text was cut short is extracted again and re-indexed on the next pass that
    reads it. Nothing needs triggering by hand. On the library this was measured on, 164 files
    were affected, which is about 60,000 more chunks to embed.
  • A key can hold a new scope, propose, which no existing key has. The key adopted from
    DEXICON__BOOTSTRAP__TOKEN is not given it, and an administrator can add it to any key with
    PUT /api/tokens/{id}/scopes. GET /api/proposals and
    POST /api/proposals/{id}/approve and /reject are new, and take the administrator's session.
    /healthz gains pendingProposals in the administrator's reply. Additive.
  • The skill changes (dexicon-skill-version: 5). Running scripts/install-mcp.ps1 again
    upgrades installed copies. The hooks are unchanged.

Added

  • A propose scope lets an agent ask for a removal, and the administrator decides
    (D-39). Two more MCP
    tools are listed for a key holding it. propose_removal asks for a source, chunk set, uploaded
    document or corpus to go, with a one-line reason, and removes nothing. removal_status lists
    the key's own requests and how each was decided, and needs no other scope and no corpus that
    still exists. propose is independent of every other scope: a key can hold it alone, and
    holding configure does not carry it.
  • An Approvals screen lists what agents have asked for. Each request says what would go and
    how much (files or commits, chunks, chunk sets), who asked and why. Approving asks first, runs
    the code the admin's delete runs, and records the approval in the same save. Rejecting leaves
    everything as it is. The nav link carries how many are waiting.

Changed

  • A settle key skips unchanged files on reindex
    (D-38).
    file_chunk_states.settled_for holds a hash of the file's size, modified time and chunking
    settings, and a pass that finds the same key does not open the file. A document (PDF, DOCX,
    PPTX, EPUB, HTML) is not opened at all; other files are still opened for the 8 KB check for a
    NUL byte. Full and Rebuild jobs ignore the key.
  • Corpus and chunk-set removal live in one service. The admin's DELETE endpoints and
    approving a request run the same code, so the two cannot drift.
  • Dependencies: lucide-react 1.52.0 and jsdom 30.1.2.
  • Test methods are named as PascalCase sentences and test classes for the behaviour they cover
    (CONTRIBUTING.md).

Fixed

  • The cached text of a PDF could be cut at its first U+0000, and the document indexed from the
    head of it.
    SQLite ends a text value there while the row recorded the full length. A cache row
    is now a hit only when what it reads back is as long as what was written; otherwise the text is
    extracted again, replaced and logged. An upload's repaired text also clears the chunk state of
    every attachment of that blob, so the repaired text is chunked and embedded.
  • U+0000 is removed from every format's text where it is stored, not only a PDF's, with the
    offsets of its units moved to match. A plain-text upload holding one was otherwise repaired,
    and chunked and embedded again, on every pass.

docker pull ghcr.io/merp4/dexicon:0.6.6

Digest sha256:d9530deae1d7b587e1e38ff31b1ac796b6ebda386a598d9fba8d6ca25304e530. An SBOM and build provenance are attached to the
image in the registry.

v0.6.5

Choose a tag to compare

@github-actions github-actions released this 07 Oct 13:50
ddc2bb2

⚠️ Upgrading

  • One migration, IndexingIsNotStored, applied at startup. Data only: a corpus or chunk set
    stored as indexing becomes degraded, once, and the next pass records what it finds.
    It cannot be undone. Nothing re-chunks or re-embeds.
  • Whether a corpus is indexing is read from its jobs and no longer stored
    (D-37). Everything that reports it
    says the same as before, but a restart no longer marks an interrupted corpus degraded:
    it keeps the outcome of its last pass, and the job that was running reads failed with
    Interrupted. A chunk set is stored degraded until its first pass finishes and reads
    indexing while that pass is queued or running.
  • The skill changes (dexicon-skill-version: 4). Running scripts/install-mcp.ps1 again
    upgrades installed copies. The hooks are unchanged.
  • A key can hold a new scope, configure, which no existing key has and the key adopted from
    DEXICON__BOOTSTRAP__TOKEN never does. PUT /api/tokens/{id}/scopes is new. Additive.
  • Refused where it was accepted before: a size cap of zero, which indexes nothing, when adding
    a source or setting a corpus default; and a corpus name that matches another's ignoring case,
    contains a colon or a control character, or is longer than 200 characters.

Added

  • A configure scope lets an agent set up what is indexed
    (D-36). Three more
    MCP tools are listed for a key holding it: list_folders, configure_corpus and
    configure_source. With them an agent lists the mounted folders, creates a corpus, changes a
    corpus's description and the filters its sources inherit, and adds or changes a files or
    history source. Nothing over MCP removes a corpus, source, chunk set, document or key. Each
    change goes through the service the UI uses and is logged with the key's name. A narrower
    filter drops what it stops selecting when the refresh runs, as in the UI, and the replies
    say so. .gitignore can be pinned on but not turned off over MCP, which is what keeps
    Dexicon's own .env out of the index where the workspace root is its checkout.
  • A key's scopes can be changed in place, on the Access page or with
    PUT /api/tokens/{id}/scopes, without issuing another key. A scope removed, or a key
    revoked, is refused from the next request.
  • index_status with a corpus named says why files are missing. Each source shows what it
    reads and its effective filters, with which come from the corpus defaults. A history source
    shows the ref it follows, how far behind its upstream it was, and its newest commit. The
    failed, skipped and empty files of the default chunk set follow, ten per status with the
    recorded reason on one line. Without a corpus named it keeps to counts.

Changed

  • Corpus and source changes are judged in one service, which the UI and the agent tools
    both call, so one cannot accept what the other refuses.
  • A corpus change is audited when it is saved, not after the request returns, so a failure
    in between leaves an entry under the key's name.
  • A chunk set cannot be deleted while a job is working on it (409). Deleting the row
    turned a job scoped to the set into one for the whole corpus.
  • Dependencies: vite 8.3.2, vitest and @vitest/coverage-v8 5.0.3, lucide-react 1.51.0,
    OllamaSharp 5.5.0, AngleSharp 1.8.3, and source-map-js 1.2.2 for GHSA-68fv-2mgg-jv7q.

Fixed

  • A corpus could stay indexing, and be skipped by the scheduled refresh, until a restart.
    A full data disk refused every write for about four minutes. The jobs in flight could not
    save their outcome, and the corpora they had marked indexing stayed so for six hours. A
    job now tries that save up to five times, and since the state is read from the jobs and the
    corpus lease it cannot be left set: a job that stopped stops counting once its lease lapses,
    and the corpus is refreshed again with nothing to repair.
  • A job whose lease claim failed was left queued and absorbed every later refresh. The
    failure was dropped with the corpus's own state, so the row read queued with no error,
    index_refresh returned that job, and nothing ran it. It is now recorded failed with the
    reason. A job that loses its lease while running records its outcome too, and leaves the
    corpus to whoever holds it.
  • A name typed with different capitals or spaces made two corpora one name could reach, or
    failed on the unique index as a 500. It is now refused as a conflict.


docker pull ghcr.io/merp4/dexicon:0.6.5

Digest sha256:42a70130293c155253211a0fbc9de526d5d7f42788c16a46883f3c0e38c06d4a. An SBOM and build provenance are attached to the
image in the registry.

v0.6.4

Choose a tag to compare

@github-actions github-actions released this 30 Sep 09:19
26fd58e

⚠️ Upgrading

  • No migrations, and nothing re-chunks or re-embeds.
  • A source added from now on, including one named when its corpus is created, follows the
    corpus's .gitignore setting and size cap until it sets its own. Sources that already
    exist keep the values they hold: the UI and corpus creation stored both on every source,
    so a corpus default for either reached none of them. To have one follow the corpus, open
    Edit source and tick Corpus default or Server default beside the field.
  • CorpusSummary gains configured: the server's .gitignore setting, its size cap, and
    the separate cap for PDF, DOCX, PPTX, EPUB and HTML files. Additive.

Changed

  • Adding a source asks what to index first. Files or Commit history opens the
    dialog, and each shows only its own settings: Which files (Include, Exclude,
    .gitignore, Size limit), or Which commits and Each commit's document holds. It was a
    checkbox after the folder picker that switched half the form's fields out. The add, edit
    and default-filter dialogs share one set of fields. Each filter has a Corpus default
    or Server default tick naming where its default comes from, and while ticked the
    field shows that value. The size limit says it is for code and text files, and gives the
    separate document limit.

  • A corpus's sources line up in columns: name, count, settings and buttons, from the
    sm breakpoint up. With thirteen sources, each row's buttons sat wherever its text
    ended. A file source's row reads .gitignore respected · code and text ≤ 64.0 MB; the
    bare ≤ 64.0 MB said nothing true about the PDFs on a shelf of books.

  • A run that re-indexed nothing says so. A refresh over an unchanged tree read
    Succeeded · 37/37 documents · 0 chunks on the corpus page; it now reads Succeeded · no documents re-indexed, 37 checked.

  • Dependencies: vite 8.3.1, jsdom 30.1.1, lucide-react 1.48.0, cn 0.4.0,
    Microsoft.Extensions.AI.Abstractions and .OpenAI 10.10.1, coverlet.collector 10.1.0.

Fixed

  • A corpus created after the page loaded gets live progress. /api/events read which
    corpora the caller can see once per connection, so a new corpus's first index showed
    nothing, and its page never re-read the counts when a run ended: a chunk set that had
    built 102 chunks stayed at "indexing · 0 chunks", and Promote offered it as holding
    0. What the caller can see is now read again on every heartbeat and on the first report
    for a corpus outside it, and the newest report of such a corpus is written once it
    becomes visible. A key whose mapping is narrowed stops receiving the corpus it lost
    within a heartbeat, where it used to keep receiving it until it reconnected.

  • The edit dialog's default for a size cap was the source's own value. A source
    pinned at 64 MB offered "corpus default (64.0 MB)", where following the default gave the
    configured 256 KB.

  • A long source path wraps instead of widening the corpus page past the window, and an
    uploaded-documents source is named as one rather than described with the file settings
    (code and text ≤ 2.0 GB, from its stored cap).



docker pull ghcr.io/merp4/dexicon:0.6.4

Digest sha256:dd86d75ac9921c1a86f6cd39833d37947bde3df095af971376ec2495c770447f. An SBOM and build provenance are attached to the
image in the registry.

v0.6.3

Choose a tag to compare

@github-actions github-actions released this 26 Sep 14:53
24664ac

⚠️ Upgrading

  • One migration, GitHistoryTracking, applied at startup. Widening only: one nullable
    column on sources, filled by the next pass over each history source. Nothing re-chunks
    or re-embeds.
  • The skill (dexicon-skill-version: 2), the SessionStart hook and the hook library
    (dexicon-hook-version: 2) change. Running scripts/install-mcp.ps1 again upgrades
    installed copies. A copy whose version marker was removed is left alone; one edited with
    its marker in place is replaced.
  • install-mcp.ps1 now registers Claude Code at user scope unless -Scope is passed. An
    install made under the old default put the skill and hooks in a project's .claude,
    which -Uninstall -Scope project -Project <that repo> removes, and registered the MCP
    server at Claude Code's local scope, which claude mcp remove dexicon --scope local, run
    in that repo, removes.

Added

  • Choose what a history source follows from the repository's own refs. The history
    editor's Ref box becomes Follow: the checked-out branch, a branch picked from the
    repository's local, remote-tracking and prefetched refs, or another ref typed in for a
    tag or a commit. Local branches say how far they are from their upstream as of the last
    fetch ("52 behind origin/main"), the remote-tracking group says when git fetch last
    ran, and a branch that is behind offers its upstream instead. A pick stores the full
    name, so a tag of the same name cannot take its place; a stored short name shows as
    picked as git resolves it, and one that resolves to a tag hiding a branch says so. The
    refs come from GET /api/workspaces/git?path=&ref=, which reads refs and FETCH_HEAD's
    time and nothing else: no remote URL and no configuration.

  • A history source's row says how current its ref was. Each pass records the branch
    the ref resolved to, its upstream and the distance, and when a fetch last ran, in
    sources.git_tracking, returned as SourceSummary.tracking for the ref it was observed
    for. The row reads HEAD (main), "52 behind origin/main as of the fetch 3d ago" in the
    warning colour, or "fetched 3d ago" for a remote-tracking ref. A read that fails keeps
    the last record and changes nothing else about the pass.

  • Keeping a mounted repository current is the host's job, and docs/04 says how.
    Dexicon does not fetch, and holds no credential for a remote. "Following a remote
    without Dexicon fetching" covers git maintenance start, whose hourly prefetch writes refs/prefetch/
    and never moves origin/main, and a scheduled git fetch, both with the host's own
    login.

Changed

  • Agents are told to name the corpus that fits. With several corpora of different
    kinds, a search across all of them lets hits from the corpora that cannot hold the
    answer take the places of the one that can: a question about one project's design,
    asked of five corpora, drew 6 of its top 10 hits from the four that did not hold it.
    The skill has a direct /dexicon-search search the one or two corpora whose
    descriptions fit and widen only when that comes back empty or thin; the SessionStart
    hook says the same when more than one corpus is listed; and search_index's corpus
    description says so to every MCP client.

  • Claude Code is registered at user scope. Its project and local scopes are keyed by
    the literal working-directory string, so a server added from one shell could be missing
    from a session started in another. The Access dialog's ready-to-paste command and every
    documented copy end in --scope user; the installer defaults to it for anything that
    only touches Claude Code, and honours -Scope project, which it used to drop. A
    project-scope .mcp.json or dexicon-hooks.env holds the key in plain text, and the
    docs and the installer say to keep it out of version control.

  • The header wraps in reading order. Narrower than about 1,056px, its chips wrapped
    inside the bar into two ragged rows; the nav and then the status group now take rows of
    their own, in the order they are read and tabbed through. A chunk set's description
    wraps beside its buttons instead of pushing them onto a row of their own.

Fixed

  • Hooks installed into a project read that project's configuration. The installer
    wrote dexicon-hooks.env into the project's .claude, and the hooks read
    ~/.claude/dexicon-hooks.env alone, so they read the user's file or none, and did
    nothing without error. They now read the file beside their hooks directory first.

  • Adding a history source when git cannot be asked is a 503 with git's reason. A
    missing git or a timeout came out as a 500.

  • git runs in the C locale. Where git has translations, the --stat summary line
    that goes into each commit's document was translated. Neither the image's git 2.54.0
    nor Git for Windows 2.31.1 translates, so nothing changes there.



docker pull ghcr.io/merp4/dexicon:0.6.3

Digest sha256:b99f95f78a4ba9196ec6d7460b7c9381810ecf0f44fcbb118f890ef17bcc414c. An SBOM and build provenance are attached to the
image in the registry.

v0.6.2

Choose a tag to compare

@github-actions github-actions released this 25 Sep 00:27
bfc2a13

⚠️ Upgrading

  • No migrations, and nothing re-chunks or re-embeds.

Security

  • Signing out ends the session on the server. The UI cleared its token before the
    sign-out request went out, so the request carried none, the server had nothing to revoke
    and answered 204, and the session stayed usable until it expired. The request now names
    the session it ends. A 401 for a session that is no longer the stored one does not sign
    the page out, so a refusal that arrives late for an old session cannot end a newer one.

Added

  • A commit limit can keep what it indexed. maxCommits alone is a window: each new
    commit pushes the oldest out of the corpus on the next pass. keepIndexed, off by
    default and refused without maxCommits, makes the limit a starting point: a commit
    once indexed stays while the ref reaches it and the other settings still select it. A
    source with keepIndexed on lists the whole reachable history on every pass. The history editor
    offers it beside a commit limit and warns before a save that turns it off, since every
    held commit past the limit then leaves on the refresh.

  • Each search hit shows its score, and Explain says how it was made: DBSF over the
    dense and sparse lists for hybrid, cosine similarity for semantic, an IDF-weighted term
    match for keyword. Explain said reciprocal rank fusion for all three, which hybrid
    stopped using in 0.3.0.

Changed

  • Promote and Revoke ask first. Each acted on one click. Promote now names the set
    search moves to and the one it leaves, with both models; Revoke says a revoked key cannot
    be restored from the page.

  • A passage marks what the keyword index matched. The terms are each word of the query
    and the parts of a compound identifier, with the index's term lengths and stopwords, and
    the passage is read token by token the same way. It marked whole query words as
    substrings, so most keyword hits for an identifier had nothing marked, and a part could be
    marked inside an unrelated word. Common words the query has on their own are still left
    unmarked.

  • Opening a hit marks all of it, wherever it is. The viewer marked only the first line,
    and loaded only the first 400,000 characters of a file, so a hit further into a book was
    neither marked nor scrolled to. It now reads on until the hit's last line is in.

  • Chunk sizes on Documents carry their units, Access shows one badge per scope, an empty
    status tab says "No failed files", and an inherited filter field is named on its line.
    docs/08 describes the Search screen as it is; it listed controls the page never had.

Fixed

  • Live progress keeps arriving on a page left open. The progress stream lost one event
    for every 20-second idle ping it had sent, so a page open for 20 minutes lost the next 60
    or so: bars froze and a finished refresh never reloaded the corpus. The connection stayed
    open, so nothing reconnected.

  • Deleting a corpus counts every chunk set it deletes. It gave the default set's count.

  • The corpus page no longer says "Run a refresh" while its files load. It drew the
    page with an empty list for one round trip on every visit. The corpus now also draws when
    the file list fails, and a listing that answers after a newer one no longer replaces it.

  • A source can be added at the workspace root. Its path is empty and the submit was
    disabled for an empty path, so the coverage notice's button for a gap at the root opened
    a form that could not be sent. A file source there now warns that it takes everything no
    deeper source claims.

  • "1 chunk", not "1 chunks", and the chunk-set summary's accessible name has spaces
    between its parts.



docker pull ghcr.io/merp4/dexicon:0.6.2

Digest sha256:cc0e1a9a5a5c244aba8aeb66f7252a53a76d42a9786aeacda9ba7bbbe60833a7. An SBOM and build provenance are attached to the
image in the registry.

v0.6.1

Choose a tag to compare

@github-actions github-actions released this 24 Sep 03:09
6d8ffba

⚠️ Upgrading

  • One migration, GitHistoryNewestCommit, applied at startup. Widening only: two nullable
    columns on sources, filled by the next pass over each history source.

Added

  • A history source shows where its ref had got to. Each pass records the newest commit
    it found, and the source's row reads newest 9d2ef06, 3d ago, with the full sha and date
    on hover; SourceSummary.newestCommit carries both. A source following HEAD in a
    checkout whose local branch nobody pulled indexed the same 174 commits for three days
    while origin/main gained 52, with every count on screen correct and nothing to say the
    history had stopped. It records where the ref points, not how far indexing got, which
    the counts and the source's state report. An inventory that fails leaves the last
    record; one whose settings select no commits clears it.

  • A history source's settings can be edited. Its row had no editor, so changing the
    ref meant the API, or removing the source and re-reading every commit. The editor holds
    the ref, the message, stat and diff with its cap, merges, a commit limit, a date and the
    include paths, and the add dialog now shows the same fields from the same component, so
    a ref such as origin/main can be chosen when the source is added. The editor says
    which kind of change it is about to save: the ref, limit, date and merges choose which
    commits are indexed and keep the documents already there, while the message, stat, diff
    and paths change every document and re-read the history.

Changed

  • History settings git cannot be asked with are refused when a source is added or
    edited.
    A malformed ref, a commit limit below one or a diff cap past the read ceiling
    was stored as sent, answered 200, and found on the next pass as the source being
    unavailable. It is now a 400 carrying the same reason the inventory gives, and the
    add and edit dialogs show it, staying open. None of the checks runs git; a well-formed ref that names
    nothing still shows on the corpus after the refresh.

Fixed

  • A dialog shows its own failures. Eleven dialogs sent a failure to the page's error
    banner, which sits under the dialog's overlay and inside the part of the page Radix
    hides from assistive tech, so a refused save left the dialog open with nothing in it to
    say why. Measured with New corpus and a name already taken: the 409's message rendered
    behind the overlay. The file and extracted-text viewers, on a load that failed, went on
    saying "Reading…" for ever. Each now shows the server's message at its top, scrolled
    into view if the dialog had scrolled past it, and stays open. A test that parses the
    source holds every dialog to it.

  • A source at the workspace root has a name. Its path is empty, and rootPath ?? kind
    passed the empty string through, so the row showed no name and its buttons were
    labelled "Remove source " with nothing after it. It is now workspace root.



docker pull ghcr.io/merp4/dexicon:0.6.1

Digest sha256:84ec1e5a2fd010dfe390183152159516ee6db709ab03c5198e1c8d4ef867baae. An SBOM and build provenance are attached to the
image in the registry.