LearnNewsExamplesServices
Frontmatter
id16591
titleThe corpus-wipe refusal is proven below the surface agents call
stateClosed
labels
aitestingarchitecture
assigneesneo-opus-ada
createdAtAug 6, 2026, 1:37 PM
updatedAtAug 8, 2026, 6:04 PM
githubUrlhttps://github.com/neomjs/neo/issues/16591
authorneo-opus-vega
commentsCount2
parentIssue16566
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 8, 2026, 6:04 PM

The corpus-wipe refusal is proven below the surface agents call

Closed Backlog/active-chunk-13 aitestingarchitecture
neo-opus-vega
neo-opus-vega commented on Aug 6, 2026, 1:37 PM

Context

Split from #16584 on @neo-opus-grace's review of PR #16590. The hazard that ticket's AC 5 named — a default-strategy manage_knowledge_base call deleting corpus rows unmetered — is closed by that PR, one layer down: the work-volume gate now counts idsToDelete and precedes every deletion, so a delete-heavy MCP call is refused before any row is removed.

What is not delivered is that AC's stated evidence location: a spec at the tool boundary rather than at VectorService. Merging #16590 with that criterion in the list would have closed a ticket whose evidence bar was not met, so it is split here rather than quietly dropped.

The Problem

The guarantee agents actually depend on is "manage_knowledge_base cannot wipe the corpus", and today that is proven only two layers below the surface an agent touches:

toolService.mjs:77   manage_knowledge_base -> DatabaseService.manageKnowledgeBase({...args, viaMcp: true})
DatabaseService:579  action 'embed'|'sync'  -> embedKnowledgeBase({viaMcp, staleStrategy})
DatabaseService:668  embedKnowledgeBase     -> VectorService.embed(dataPath, {viaMcp, staleStrategy})

staleStrategy is optional at every hop, so omitting it inherits the destructive default. The gate protects the outcome, but nothing pins the contract at the boundary — a future refactor could thread viaMcp differently, or add a caller that bypasses embedKnowledgeBase, and every existing spec would stay green.

The Architectural Reality

The reason this is its own ticket rather than three more lines in #16590: the MCP dispatch path cannot be exercised naively. services.mjs wraps each service in a Zod-validating Proxy (makeSafe), and a closure-injected viaMcp: true is stripped before it reaches the gate unless the flag is declared in the OpenAPI contract. KBBackupRestoreWipe.integration.spec.mjs already documents that exact trap for the sibling path, and the input-side strip class is #16585.

So a faithful boundary spec must choose deliberately between:

  • DatabaseService.manageKnowledgeBase — one layer above VectorService, the tool's own service entry, no Proxy in the way. Cheap and honest, but not literally the MCP surface.
  • Full callTool('manage_knowledge_base', ...) dispatch — the real surface, but it must first establish that viaMcp survives makeSafe, which is contract work rather than test work.

Picking between those, with the reasoning recorded, is part of this ticket.

The Fix

  • A spec proving manage_knowledge_base {action: 'embed'} and {action: 'sync'} without staleStrategy cannot delete corpus rows: assert the rows still exist after the call, not merely that a refusal payload came back.
  • A negative control: the same call with an explicit destructive strategy and a below-threshold volume still deletes, so the spec is not passing because the path is unreachable.
  • Record which boundary was chosen and why the other was rejected.

Decision Record impact

none. Test coverage for an existing contract; no runtime behaviour changes.

Acceptance Criteria

  • A default-strategy manage_knowledge_base embed/sync with a large idsToDelete is refused, and the rows are still present afterwards.
  • Negative control: an explicit destructive strategy below the threshold still deletes, proving the assertion above is not vacuous.
  • The chosen boundary is stated in the spec's docblock, with the rejected alternative and its reason.
  • If full MCP dispatch is chosen, viaMcp reaching the gate is proven rather than assumed — otherwise the spec silently tests the un-gated path.

Out of Scope

  • The scoping fix and the gate changes themselves — delivered by #16590.
  • Declaring viaMcp in any additional contract, or the parity guard for undeclared params — #16585.
  • The neo/neo shared-stamp collision — epic #16566.

Related

  • #16584 / PR #16590 — the fix whose AC 5 evidence bar this carries.
  • #16585 — the Zod-strip class that makes the dispatch path awkward to test.
  • #16587 — the same strip mechanism, in production.

Origin Session ID: 6004a4aa-2089-4b14-b73f-b58c08cf53d9

Retrieval Hint: query_raw_memories("manage_knowledge_base tool boundary spec delete refusal") · DatabaseService.mjs:579-668

Authored by @neo-opus-vega (Claude Opus 5).

tobiu referenced in commit 0d5f817 - "test(ai): the corpus-wipe refusal is proven at the surface agents call (#16591) (#16714)" on Aug 8, 2026, 6:04 PM
tobiu closed this issue on Aug 8, 2026, 6:04 PM