Repository navigation
fix(autoindex): remove one folder safely and refuse wildcard deletes (#2114) - #2126
Conversation
A coding agent deleted a live ax-docs index after a prefix-collision refusal (#2114). Every folder defaults to the prefix ax, the refusal did not lead with a distinct --prefix, nothing listed one folder and deleted only that, and DELETE /ax-* was accepted. xerj autoindex remove --prefix P [--root DIR] reads the catalog, prints indices, catalog rows, doc counts and store sizes, and deletes nothing unless a terminal answers y or the caller passes --yes. Two folders under one prefix still require --root, including with --yes. An index another folder still names, a shared edges index, and autoindex-catalog are left in place. MCP xerj_autoindex_remove is the same plan and deletes only when confirm is true. DELETE of *, _all, or any wildcard is a 400 unless action.destructive_requires_name is false, matching ES 8. The YAML runner turns the guard off for DELETE /_all cleanup and puts the key back to null. The prefix-reuse refusal now leads with a ready-to-copy xerj autoindex <folder> --prefix <name> and says not to delete the other folder's indices. Verified locally with rustc 1.97.1, debug builds: - cargo fmt --all - cargo clippy --locked -p xerj-autoindex -p xerj-api -p xerj-mcp -p xerj-server -p xerj-console-api -p es-yaml-runner --all-targets -- -D warnings (exit 0) - cargo test --locked of those crates: xerj-api integration tests passed, including destructive_requires_name (3) and corrupt_index_http (11); xerj-autoindex --lib 1560 passed, 2 ignored; xerj-mcp and xerj-server passed. xerj-console-api --lib aborted on the default thread stack (SIGABRT, stack overflow in a tokio worker) and passed 67/67 with RUST_MIN_STACK=33554432. - es-yaml-runner against a debug --insecure node on :9200: 1438 passed, 0 failed, 10 skipped (227 files). Not run: a release rebuild of the touched crates. Co-authored-by: xerj-org <xerj-org@users.noreply.github.com>
A dataset row matches `ax` when its index name starts with `ax-` and its slug is the rest of the name. A legacy row with no slug only needs the `ax-` start, so `ax-2-docs` (prefix ax-2) counted as ax's. Rows whose run_id names a run of another prefix, and slug-less rows under a longer sibling prefix the catalog knows about, are now never in the delete set.
|
Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Xerj Merger.
|
|
Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Xerj Merger.
|
|
Xerj Merger made the fix: Removing Destructive-path review:
Notes:
Local CI passed (fmt, clippy |
…r sweep (#2136) * release(rc.98): version stamp, CHANGELOG, ROADMAP roll, landing footer sweep Window since v1.0.0-rc.97 (about 120 PR merges on main's first-parent line, every one cited in [1.0.0-rc.98]; the release-notes gate passes locally). Stamp: workspace 1.0.0-rc.97 -> 1.0.0-rc.98 (Cargo.toml + Cargo.lock). CHANGELOG: [Unreleased] moves into [1.0.0-rc.98], plus the window's uncited merges grouped by area; the rc.97 heading gets its tag date (2026-10-19). ROADMAP review line + roll: open defects stay at five. Landing footer sweep RC.97 -> RC.98, the data-latest-tag fallback, and the regenerated sitemap. * release(rc.98): cite #2124, #2126 and #2127 * Merge main into release/rc98 * release(rc.98): one #2124 entry * release(rc.98): intro names #2128 and #2126 * release(rc.98): regenerate sitemap after the merges --------- Co-authored-by: Xerj Merger <merger@xerj.org>
What this changes, and why
A coding agent deleted a live
ax-docsindex afterxerj autoindexrefused a second folder under the default prefixax(#2114, with #2115 and #2116). Three gaps made that easy:--prefix, so "this prefix is taken" read as a cleanup task.DELETEplus_delete_by_queryonautoindex-catalog, with no preview and no confirmation.DELETEof*,_all, or any wildcard was accepted, while closing an index by wildcard was already refused. ES 8 refuses those deletes by default (action.destructive_requires_name=true).xerj autoindex remove --prefix P [--root DIR]readsautoindex-catalogfirst and prints the roots, indices, catalog rows, doc counts and store sizes. It deletes nothing unless a terminal answersyor the caller passes--yes. A non-TTY run (an agent) prints the plan as JSON and exits 4. Two folders under one prefix still require--root, and--yesdoes not choose a folder. An index another folder still names, a shared edges index, andautoindex-catalogare left in place. MCPxerj_autoindex_removeis the same plan and deletes only whenconfirmis true.DELETE /{index}now returns 400Wildcard expressions or all indices are not allowedwhen the selector is*,_all, or contains*or?, unlessaction.destructive_requires_nameisfalse. The ES-YAML runner turns that guard off for itsDELETE /_allcleanup and puts the key back tonull, so the next file sees the default again.indices.delete/10_basic.ymlopts out for its expansion cases;20_destructive_wildcard.ymlasserts the refusal.The prefix-reuse refusal now starts with "do not delete the other folder's indices" and a ready-to-copy
xerj autoindex <folder> --prefix <name>. Agent docs (AGENTS.md,llms.txt,llms-full.txt, the MCP tool descriptions) say neverDELETEan index this session did not create, and to usexerj autoindex remove.Rebased onto
origin/mainafter--walk-only(#2006).removerefuses--walk-onlyrather than ignoring it.Evidence
The acceptance checks covered by those runs:
removewithout--yessends noDELETEand exits 4 with the root list;--yeswithout--rootstill deletes nothing when two folders share the prefix;--root A --yesdeletes only A's index and catalog rows; an index both roots name (ax-docs) is kept;DELETE /ax-*,/_alland/*are 400 untilaction.destructive_requires_nameis false, then/ax-*is 200 and does not delete a non-matching index; the prefix-reuse message names a distinct--prefixbefore--replace-prefix.Checks
cargo fmt --all(CI runsrustfmt --checkandclippy -D warnings)cargo build --release -j 32 -p <crate>cargo test -p <crate>passes (cargo checkis not sufficient — test code is interleaved with production code)cargo run --release -p es-yaml-runner -- --dir tests/es-compat-yaml/yaml), or: not applicable because this change is docs/landing-onlyengine/tests/es-compat-yaml/yaml/docs/CONTRIBUTION_REVIEW.mddocs/CONTRIBUTION_REVIEW.md. Record a negative control that exits non-zero with no artifact extracted, installed, published, or served. Keep verification and its action in one block; rerun the control with the failing exit discarded and with the next documented block appended, and require that neither reaches an artifact or unsafe/privileged path. List tested variants and unsupported variants separately, and verify every publication copy. Otherwise mark this itemN/Awith a reason.Not run: a release rebuild. The ES-YAML number above is from
target/debug/xerjandtarget/debug/es-yaml-runner, notcargo run --release. Clippy was the touched crates, notcargo clippy --workspace.xerj-console-apipassed only withRUST_MIN_STACK=33554432.The recipe edit is one sentence inside the existing
--prefixdescription indocs/recipes/zero-config-indexing.md. It adds no shell, TOML, or systemd block, so the operational-recipe negative-control audit is N/A. Failure atomicity forremoveis covered by the partway-delete path: a failed delete stops, and the JSONremainingobject is a fresh catalog read. That path is unit-tested for the planner and the no-delete confirmation; a mid-bulk HTTP failure against a live node was not simulated.Open PRs #1909 and #1925 also edit
landing/llms.txt. This branch adds one caveat bullet.Provenance
cursor/autoindex-safe-remove-da05destructive_requires_nameand in the YAML files.removewithout--yeswas observed against a local mock catalog server (noDELETEin the request log). The shared-index case (ax-docsnamed by both roots) passed inan_index_another_root_still_names_is_not_deleted.search_kind; it was not driven with 10,000 real documents). A release build that failed, or a YAML file that failed only under--release, would falsify the debug result.