Commit 4368ea6
authored
Orphan-chunk fixes (cancel + startup GC), nested .gitignore, human time bounds, and docs reconciliation (#124)
* engine: purge orphan chunks when a pipeline object is cancelled mid-embed
The shared EmbedConsumer upserts an object's chunks to Milvus before its
finalize hook writes the objects row. If the task was cancelled out from under
it (an external job cancel, or a connector removal racing the consumer), the
hook lost only the object_tasks 'succeeded' claim while still committing the
chunks and the objects row, leaving chunks that no objects row points at.
Those orphans are un-inspectable and un-cat-able (404) yet still match search,
which queries Milvus directly.
Claim completion first (guarded UPDATE ... WHERE status='running', via
execute_rowcount); when the claim is lost, delete the just-upserted chunks and
skip the objects row / cursor advance so a later sync re-does the object
cleanly. Carry connector_uri in the finalize context for the delete.
* mfs-ingest skill: drop jq from example snippets
The job-poll loop and connector-list/ls examples piped through jq, which is not
a project dependency and is absent on many machines, so the snippets failed
silently. Use grep on the JSON status field, awk on the connector-list column,
and plain --json output the agent reads directly.
* engine: reconcile orphan chunks at startup
Adds a cost-guarded startup GC that deletes Milvus chunks no committed objects
row points at — the residual window the per-object self-heal can't cover (a
consumer crash between upsert and finalize, or pre-existing orphans). It first
compares the Milvus row total against the sum of committed chunk_counts and
returns immediately when they match, so a healthy index pays only a couple of
count() aggregates; the distinct-object scan + per-object delete run solely when
there is genuine excess. Both the count and the scan are scoped to non-summary
chunks via MilvusStore.distinct_objects/GC_SCOPE_EXPR, since directory summaries
legitimately have no objects row and must never be collected.
* api: clarify the ls 'indexable' field is eligibility, not searchability
indexable=True only means an object is not binary and not opted out; data/config
kinds (yaml, jsonl, csv) are eligible but never embedded. Agents and users were
reading it as 'searchable'. The real signal is search_status — say so on the field.
* docs: rewrite CONTRIBUTING with project background, architecture, and a connector guide
Adds a 'what MFS is' + architecture-at-a-glance section and a substantial
'Contributing a connector' guide that carries the design intent — thin connector
on a fat framework, required vs optional methods, honest Capabilities, object
naming conventions, boundary rules, the four-sources consistency rule, and a
pre-work design checklist.
Reconciled against the current code rather than the old design notes: connectors
are flat (__init__.py + plugin.py), registration is register() + the
load_builtin() import list (no REGISTRY dict), the dataclass is PathStat (not
FileStat), there is no _contract.py harness (fakes under tests/), and PROMPT is a
short inline string rather than a PROMPT.md auto-collected into the skill. Also
fixed stale bits in the existing file (skills/mfs path, $MFS_HOME default,
v0.4.0 wording, mfs job list/show command names).
* docs: add CLAUDE.md for agent onboarding, symlinked as AGENTS.md
Captures the big-picture architecture (thin Rust CLI over a Python server that
owns connectors / ingest / index / storage), the non-obvious build/test commands,
the object-row/Milvus invariant, the four-sources connector-config rule, and
lockstep release mechanics. AGENTS.md is a symlink so both the CLAUDE.md and
AGENTS.md agent-file conventions resolve to one file.
* docs: fix chunk_kind names (body, not chunk_body/file/code/image)
The text chunk kind is 'body'; file/code/image are object kinds, not chunk
kinds. mfs-find's --kind list and connector references said 'chunk_body' (so
'--kind chunk_body' matched nothing), and ingest-pipeline.md listed file/code/
image as chunk_kinds. Align all of them with connectors/base.py ChunkKind.
* connectors: reconcile config fields with the plugins across the four sources
- jira: Server/DC PAT goes in 'token' (the plugin's PAT auth path), not
'api_token' (which is the Cloud email+token path). Fix the skill + add a
'token' field to the wizard.
- slack: 'oldest' is passed straight to Slack and must be a unix timestamp;
'now-30d' is never parsed. Fix skill/docs/wizard, and drop the false claim that
slack honors --since (it has no since_pushdown).
- web: 'max_pages' default is 50, not 100 (skill table + wizard default).
- file: drop 'max_file_bytes' from the file connector docs — FileConfig has no
such key and it is read nowhere.
* docs: correct inaccurate command and behavior descriptions
- providers: image-description/summary setup is 'setup --section description'
(and --section summary); there is no 'vlm' section.
- search-and-browse: 'cat --range' requires an explicit colon — a bare '120' is
rejected, not treated as '120:'.
- ingest-pipeline: select_producer maps 8 object kinds onto 5 producer classes
(not one per kind).
- schema: the cache_key also folds in the call kind and config.
- cli: match the real em-dash in the queued-job message.
* deployments: compose uses MILVUS_URI/MILVUS_TOKEN, not the unread MFS_MILVUS_* names
The server config reads MILVUS_URI / MILVUS_TOKEN (and ZILLIZ_*); MFS_MILVUS_* is
read nowhere, so a Compose-supplied Zilliz endpoint was silently ignored and the
server fell back to Lite. Map the host ZILLIZ_URI/ZILLIZ_TOKEN onto the real
MILVUS_URI/MILVUS_TOKEN the server actually reads.
* docs: minor accuracy fixes (cargo-fmt scope, s3 region default, artifact-kind docstring)
- CLAUDE.md / CONTRIBUTING.md: CI cargo-fmt checks both cli and server-rs.
- s3 skill: the connector sets no region default (boto3/AWS env decides), not us-east-1.
- ArtifactStore docstring: drop the stale 'vlm_text' artifact kind — VLM output
lives in the transformation cache, not the artifact cache.
* file connector: honor nested .gitignore/.mfsignore, not just the root's
The walk fed a single flat pattern list (DEFAULT_IGNORE + only the root
.gitignore/.mfsignore) to walk_tree, so a subproject that ignores its build dir
via its own nested .gitignore (e.g. server-rs/.gitignore '/target') had that dir
walked and indexed — inflating object counts and pegging CPU on dev trees.
Collect every .gitignore/.mfsignore in the tree and translate each line to a
root-relative pattern (anchored lines scoped to their dir; floating lines become
'<dir>/**/<line>'), folding git's per-directory semantics into the one flat list
walk_tree consumes. This fixes both the pure-Python and the native walkers with
no Rust change, and prunes ignored dirs as it descends (so .venv / a nested
build dir is never entered). list() uses the cheaper root..dir ancestor chain.
* connectors: accept human time bounds (ISO date / now-30d / unix ts)
slack's 'oldest' previously required a raw unix timestamp and was passed straight
to Slack — out of step with feishu's --since, which already took an ISO date, and
a poor UX (nobody hand-computes an epoch). Add a shared common.timeparse
parser (ISO date / datetime, relative now-Nd|w|h|m, or a unix ts) and use it for
slack 'oldest' and feishu '--since'. Docs/skill/wizard describe the friendly forms
again — now backed by the code. Slack still has no --since (no since_pushdown);
'oldest' is its bound.1 parent ce1e2c6 commit 4368ea6
39 files changed
Lines changed: 1144 additions & 147 deletions
File tree
- deployments/compose
- docs
- connectors
- server/python
- src/mfs_server
- api
- common
- connectors
- feishu
- file
- slack
- engine
- producers
- server
- storage
- tests
- skills
- mfs-find
- reference/connectors
- mfs-ingest
- reference
- connectors
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
0 commit comments