LearnNewsExamplesServices
Frontmatter
id16550
titlerestore.mjs cannot restore into a non-canonical target, so a restore defect cannot be reproduced without writing to production
stateClosed
labels
bugaitesting
assigneesneo-opus-vega
createdAtAug 5, 2026, 3:45 PM
updatedAtAug 5, 2026, 7:04 PM
githubUrlhttps://github.com/neomjs/neo/issues/16550
authorneo-opus-vega
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 5, 2026, 7:04 PM

restore.mjs cannot restore into a non-canonical target, so a restore defect cannot be reproduced without writing to production

Closed Backlog/active-chunk-13 bugaitesting
neo-opus-vega
neo-opus-vega commented on Aug 5, 2026, 3:45 PM

Refs #16549 — this blocks that ticket's discriminating experiment.

Context

The agreed reproduction for the in-place corpus loss was: restore a small slice by the same --mode merge --only-substrate=kb path into a throwaway collection name, verify retrieval, delete one document, re-count.

That invocation does not exist. restore.mjs's full argument surface is --mode merge|replace, --force, --force-topology-mismatch, --only-substrate=<csv>, --preserve-read-state. There is no --collection, no --target-name, no rename option — the collection is resolved from config, not from an argument.

So run as designed it would have restored 61,206 rows into the live canonical neo-knowledge-base: the exact collection under investigation, which we had explicitly agreed not to touch. Caught by reading the flags before firing rather than after.

The Problem

A restore tool that can only write to production cannot be used to diagnose itself.

Every property worth testing about a restore — does it land, is it durable across a restart, does it survive the first mutation, does a partial bundle behave — requires performing one. With no target override, each of those experiments is indistinguishable from a production restore, so the safe choice is to run none and the available choice is to risk the corpus.

That is the shape that makes an incident un-diagnosable: the tool's blast radius equals its only usable configuration.

The Fix

Not prescribed in detail; one deliberate open question below. The shape:

  1. A target override — a flag that redirects the restore's collection target to a caller-supplied name, so a slice can be restored somewhere disposable.
  2. It must be inexpressible to accidentally alias a canonical name. The override should refuse a canonical target rather than trusting the caller — the DestructiveOperationGuard's uniform gate is the precedent, and it already fires for every destructive collection-delete regardless of name.
  3. A --dry-run would cover a different and cheaper class: confirming what a restore would write without writing it. Worth considering alongside, not instead.

Open Question

OQ1 — is a target override the right shape, or should the diagnostic path avoid restore.mjs entirely? The alternative is a purpose-built probe that exercises collection.add() durability directly, with no bundle and no restore semantics. That is simpler and has a smaller blast radius, but it tests the client path rather than the restore, so it cannot catch a defect that lives in bundle parsing, id preflighting, or chunking. Whoever takes this should settle which property is actually under test before adding a flag — a flag that invites production-shaped experiments into a diagnostic role may be worse than no flag.

Acceptance Criteria

  • A restore can be directed at a non-canonical collection target, or OQ1 is resolved in favour of a separate probe and that probe exists.
  • A canonical target cannot be reached through the override — attempting it refuses, rather than relying on the operator to type carefully.
  • The refusal is asserted in both directions: a disposable target succeeds, a canonical one is rejected. The second is the assertion that stops the guard being satisfied by rejecting everything.
  • The reproduction in #16549 becomes runnable without writing to a live corpus.
  • restore.mjs's JSDoc states the target contract at the argument, so the next person designing an experiment does not assume a capability that is absent — that assumption is what produced this ticket.

Out of Scope

  • The durability defect itself#16549 owns it. This ticket only makes it testable.
  • Changing --mode merge / --mode replace semantics. The two-mode contract and its guards are working as documented; the gap is target selection, not destructiveness.
  • Backup-side tooling. Bundle production is unaffected.

Avoided Traps

  • Adding the flag without a refusal. An override that can name a canonical collection turns a diagnostic convenience into a faster path to the incident it exists to study.
  • Assuming the flag is obviously right. OQ1 is real: a purpose-built durability probe may be the better answer, and it tests less. Naming which property is under test has to precede the mechanism.
  • Treating this as documentation-only. The missing JSDoc contributed, but a comment would not have made the experiment runnable — the capability is genuinely absent.

Related

  • #16549 — the durability defect whose discriminating experiment this unblocks.
  • #16521 · #16510 — the restorability-verdict lane; adjacent, and unaffected by target selection.

Live latest-open sweep: recent open issues plus targeted restore/durability searches run for #16549 in the same pass; nearest neighbours #15639 and #15693 are restore features and neither addresses target selection or diagnosability.

Origin Session ID: 11695cce-9854-4be2-80c3-8ea4322298bf

Retrieval Hint: query_raw_memories("restore.mjs no target collection flag cannot reproduce restore defect without writing to production canonical guard")

tobiu closed this issue on Aug 5, 2026, 7:04 PM