LearnNewsExamplesServices
Frontmatter
id16302
titleA deployment can have no off-host backup copy and nothing says so
stateClosed
labels
bugai
assignees[]
createdAtAug 1, 2026, 11:28 PM
updatedAtAug 1, 2026, 11:55 PM
githubUrlhttps://github.com/neomjs/neo/issues/16302
authorneo-opus-grace
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 1, 2026, 11:55 PM

A deployment can have no off-host backup copy and nothing says so

neo-opus-grace
neo-opus-grace commented on Aug 1, 2026, 11:28 PM

Context

Surfaced 2026-08-01 by @tobiu while reviewing a client redeploy checklist, and confirmed on our own plane. The mechanism for off-host backup sync already exists and shipped — #15641 built the hook and its receipts, #16199 made a required off-host run fail after the local receipt. Both closed. This ticket is not about building it.

The gap is that a deployment can have no off-host copy and nothing says so.

Live evidence from this plane's most recent bundle receipt:

"offHostSync": { "status": "disabled", "exitCode": null, "durationMs": null }

"disabled" is reported exactly the way a healthy value is reported. Nothing distinguishes "this deployment deliberately accepts local-only backups" from "nobody ever configured this and no one noticed." Today those two states are byte-identical in the receipt.

Precedent, not hypothesis: a deployment lost ~30 memories to this exact shape — one backup root, no off-host copy, and a redeploy that recreates containers. Small blast radius that time because the corpus was smoke-test data. The shape is what matters, not the count.

Live latest-open sweep: latest 12 open issues at 2026-08-01T21:27:41Z. Search sweep on offHostSync / off-host backup across all states: #15641, #16199, #16055, #16201, #16203 all closed; #15639 (opt-in first-boot restore) is adjacent, not equivalent — it covers restore into a fresh deployment, not whether a copy exists to restore from. No A2A [lane-claim] on this scope.

The Problem

The off-host posture is an undeclared default rather than a decision.

#16199 correctly fails a run when off-host sync is required and does not complete. But nothing requires a deployment to state whether it is required. A deployment that never sets it sits at disabled forever, produces green backup receipts indefinitely, and the first evidence that no remote copy exists arrives when someone needs one.

That is the same class as the failures this repo has been closing all week — #16240 (a zero-row bundle reported as a successful backup), #16246 (a dead route reporting active), #16258 (a route never attempted, so never failed). In every case the system was correct about the step it measured and silent about the property that mattered.

Concretely on this plane today, all three true at once:

  • offHostSync.status: "disabled" — one copy exists, on one disk.
  • That copy lives at …/neomjs/neo/ai/deploy/.neo-ai-data/backups, inside a git checkout and gitignored. git status will never show it; git clean -xdf will delete it.
  • The backup lane reports success. Nothing in the receipt, the health surface, or the deployment state says the deployment has no off-host copy.

The Architectural Reality

  • ai/configBase.mjs:1567 — the offHostSync config block; :1347 documents that a non-empty maintenance.backup.offHostSync.command naming an executable is what arms it.
  • ai/services/memory-core/helpers/offHostSyncStore.mjsbuildBackupReceipt, which records the offHostSync result block quoted above.
  • #16199's failure path fires only when the run is required; disabled is not a failure and correctly is not treated as one.
  • This is deliberately not a destination question. Where a deployment syncs to (object store, second host, operator-managed) is infrastructure, varies per deployment, and is not ours to pick. The ticket is about making the absence legible and the choice explicit.

The Fix

(Prescription — inference, not observation.)

Require the posture to be declared, and make an undeclared one visible:

  1. A deployment states its off-host posture explicitly — required, or an acknowledged local-only. An unset value is neither, and should not silently resolve to "fine".
  2. local-only is recorded where an operator sees it — the backup receipt and the deployment-state surface — as an accepted risk, not as a status word that looks like every other status word.
  3. A bundle whose deployment declares required and has no completed off-host result is already handled by #16199; nothing here changes that path.

The bar to hold: after this, "does this deployment have an off-host copy?" is answerable from a surface, without reading a config file and inferring from an absence.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
off-host posture configBase.mjs offHostSync block posture is declared (required | acknowledged local-only); unset is not a silent pass unset ⇒ surface it as undeclared, never as healthy — surfacing must not itself fail a backup run config JSDoc + the local-agent-os README a deployment with no off-host copy is distinguishable from one that has a verified copy, without reading config
backup receipt offHostSyncStore.buildBackupReceipt receipt carries the declared posture alongside the run result absent posture recorded as undeclared, not omitted receipt schema note a receipt answers "should there have been a remote copy?"

Acceptance Criteria

  • A deployment's off-host posture is declared, and an undeclared posture is visibly distinct from an acknowledged local-only on at least one operator-facing surface.
  • The backup receipt records the declared posture alongside the sync result, so a bundle is self-describing about whether a remote copy was expected.
  • An acknowledged local-only deployment still produces green backups — this must not become a gate that blocks a legitimate local-only setup.
  • #16199's required-and-failed path is unchanged, pinned by a spec that fails if the failure is softened.
  • Surfacing an undeclared posture never fails the backup run itself; a reporting change must not create a new way to lose a bundle.
  • Post-merge: this plane's receipt states its posture, and the answer to "is there an off-host copy?" comes from a surface rather than from reading configBase.mjs.

Out of Scope

  • Choosing a sync destination. Infrastructure and per-deployment; an operator call, not a code change.
  • Backup contents, cadence, retention, or the bundle format.
  • The backup root's location — #16201 moved the default out of the working tree and is closed; that this plane's bind still resolves into a checkout is a deployment-config artifact of the image lag, not this ticket.
  • Restore behaviour — #15639 owns opt-in first-boot restore.
  • The receipt's missing integrity field on this plane. That is #16240 shipped-but-not-deployed (running image is 15 merged PRs behind), not a defect to fix here.

Avoided Traps

  • Rebuilding the sync mechanism. It exists (#15641) and the required-failure path exists (#16199). A ticket that re-implements either would duplicate closed work; the gap is declaration and visibility only.
  • Making local-only a failure. A legitimately local-only deployment must keep working. The defect is silence, not the choice.
  • Picking a destination to "finish" the ticket. That converts an operator decision into an agent one and will be wrong for deployments we do not run.
  • Reading disabled as evidence of a decision. It is the unset default. Treating it as an accepted risk that someone already weighed is precisely the inversion this ticket exists to remove.

Related

  • #15641 — built the off-host sync hook and receipts (closed)
  • #16199 — fail required off-host runs after the local receipt (closed); this adds the declaration that makes required reachable
  • #16203 — backups and the graph sharing one disk (closed); same failure-domain concern, storage axis
  • #16240 — a zero-row bundle is not a recovery source; the sibling "green receipt, unusable artifact" shape
  • #15639 — opt-in first-boot restore; the consumer of whatever copy this guarantees exists
  • D#16193 — deployment/IaC; a relocatable Agent OS needs a restorable-from-elsewhere corpus, which a local-only posture cannot provide

Origin Session ID: 713db0da-2239-44ea-ba5b-931be90d34fc

Retrieval Hint: query_raw_memories("offHostSync disabled undeclared posture backup receipt local-only accepted risk"), or ai/configBase.mjs offHostSync.