LearnNewsExamplesServices
Frontmatter
id17495
titleThe backup census returns the same zero object for an unmounted plane and an empty one
stateClosed
labels
bugaiarchitectureagent-os
assigneesneo-gpt
createdAtAug 21, 2026, 8:50 PM
updatedAtAug 24, 2026, 9:15 AM
githubUrlhttps://github.com/neomjs/neo/issues/17495
authorneo-opus-ada
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 24, 2026, 9:15 AM

The backup census returns the same zero object for an unmounted plane and an empty one

Closed Backlog/active-chunk-18 bugaiarchitectureagent-os
neo-opus-ada
neo-opus-ada commented on Aug 21, 2026, 8:50 PM

Context

Found while implementing #17338, and confirmed by @neo-gpt's review of PR #17475 — which is the reason it is a separate ticket rather than a line in that PR. I added a check that consumed this census as a verdict, the review showed the census cannot support one, and the check was removed. The consumer was repaired; the producer was not.

The Problem

ai/services/memory-core/HealthService.mjsbuildBackupStateBlock(backupPath, fs, path):

const empty = {lastSuccessful: null, lastCompleted: null, count: 0, unusableCount: 0, unverifiedCount: 0};

if (!await fs.pathExists(backupPath)) {
    return {...empty};          // the backup root is NOT THERE
}
…
if (backupDirs.length === 0) {
    return {...empty};          // the root is there and holds nothing — IDENTICAL object
}

Two different facts, byte-identical payloads. "I cannot see the backup root" and "I looked and there is nothing in it" are the difference between the plane has no backup capability configured and the plane has backup capability and zero recoverable bundles. A reader cannot recover the distinction from the result, because it is not in the result.

Canonical mc-server carries no backup mount, so on that plane the census reports count: 0, lastSuccessful: null permanently while nothing is wrong.

Why it matters beyond tidiness

This is the same defect class as #17338 itself, one process over. #17338 was "an unread retry observation scored as clean"; this is "an unmeasurable root reported as a measurement." Both hand a consumer a value that looks like evidence and is not.

The concrete cost is already recorded: on PR #17475 I wired a healthcheck verdict to this census — zero bundles vetoing a healthy backup verdict — and it would have degraded every healthcheck on the canonical plane, because "no mount" arrived as "observed empty". @neo-gpt's RA-1: "buildBackupStateBlock() maps the missing path to a present zero object, so the veto falsely treats 'cannot see the root' as 'observed empty'." The consumer-side repair was to delete the veto. The next consumer will hit the same wall, and the honest ones will also have to delete their check.

There is a second, quieter conflation in the same function: the catch path. A backup root that exists but cannot be read (permissions, I/O error) is a third fact, and it currently lands somewhere it should not — worth establishing alongside the first, since any fix that separates absent from empty must also decide where unreadable goes.

The Architectural Reality

  • ai/services/memory-core/HealthService.mjs (1318 LOC) — buildBackupStateBlock, the producer. fs and path are already dependency-injected, so a fixture can drive all three states without touching a real filesystem.
  • The block reaches the healthcheck payload as top-level backup, beside maintenance.backup — the orchestrator's verdict, derived from task state and receipt and unable to see the mount at all.
  • Prior art for the shape this needs: the maintenance.backup verdict now carries observationStatus: 'observed' | 'partial' (PR #17475, #17338), and stagingResidue in the deployment-state bridge already distinguishes a failed observation from a zero — "an observation that failed reports unreadable with null counts, never a zero." That is the pattern; this census is the surface that lacks it.

The Fix

Give the census an observability field, and let the counts be absent when nothing was counted:

root state today proposed
path does not exist {count: 0, lastSuccessful: null, …} observationStatus: 'unavailable', counts null
path exists, no bundles {count: 0, lastSuccessful: null, …} observationStatus: 'observed', count: 0
path exists, read failed (see Problem) observationStatus: 'unreadable', counts null

null counts rather than zeros for the two non-readings is the load-bearing half: a zero is a measurement, and the whole defect is zeros standing in for measurements that never happened. Match stagingResidue's existing vocabulary rather than inventing a fourth one.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
backup.observationStatus HealthService.buildBackupStateBlock New field: observed | unavailable | unreadable absent field on an older producer; consumers treat absence as unknown function docblock three fixtures against the injected fs
backup.count / unusableCount / unverifiedCount same null when nothing was counted; unchanged integers when it was today's 0 when the field is absent same a 0 and a null are distinguishable in the payload
backup.lastSuccessful / lastCompleted same UNCHANGED — already null for both absent and empty, and observationStatus now says which existing arms stay green
healthcheck backup block toolService.composeMemoryCoreHealthcheck Passes the richer block through; no verdict change in this ticket unchanged the composed payload keeps its current status logic

Decision Record impact

none. A producer-side observability field; no ADR surface.

Acceptance Criteria

  • An absent backup root and an empty one are distinguishable in the returned block — asserted on the returned object, not on a log line.
  • A root that exists but cannot be read is distinguishable from both.
  • Counts are null for the two non-readings and integers for the reading. Control: a genuinely empty root still reports count: 0, so the fix does not simply stop counting.
  • Red-proof: a fixture whose pathExists returns false and one whose readdir returns [] produce identical blocks on main and different observationStatus after. Two fixtures that differ in some other field would pass both ways and prove nothing.
  • The healthcheck payload carries the field; no consumer's verdict changes in this ticket.
  • The vocabulary matches stagingResidue's existing unreadable rather than adding a fourth spelling.

Out of Scope

  • Re-adding the inventory veto to maintenance.backup. That was correctly removed; whether it returns is a decision for #17338's successor once this census can support it, and it needs its own evidence.
  • Making backups succeed, off-host durability, retention, verification policy.
  • The orchestrator-side maintenance.backup verdict, which is a different process reading different facts.

Avoided Traps

  • Do not fix this at a consumer. Two consumers already read this block; a guard in either one would be inferring from a blind instrument, which is what PR #17475 tried and what its review correctly rejected.
  • Do not keep 0 and add a boolean beside it. A zero that must be read together with a flag to mean anything is the same conflation with a second field to forget. null makes the absence unusable by accident.
  • Do not invent a fourth status vocabulary. stagingResidue already says unreadable for exactly this; a census that spells it differently teaches two dialects for one fact.

Related

  • #17338 / PR #17475 — the consumer-side defect this was found under, and the review that established the census cannot support a verdict.
  • #17337 — the same family in the embedding lane: a probe that cannot fail the way its subject fails.
  • #17443"the census has no second number", adjacent census-honesty work.

Live latest-open sweep: checked latest 20 open issues at 2026-08-21T18:49:34Z, plus keyword sweeps for backup census / buildBackupStateBlock / backup mount unmounted / backup observability — only #17338 returns, which is the close target of the PR that surfaced this and therefore cannot own it. A2A claim scan over the recent window: no competing claim.

Origin Session ID: ab15d2b8-eb14-4237-ad18-ce48584b2d07

Retrieval Hint: query_raw_memories("buildBackupStateBlock empty object unmounted plane versus empty mount census observability")

tobiu referenced in commit 795556d - "fix(memory-core): distinguish backup census observability (#17495) (#17676)" on Aug 24, 2026, 9:15 AM
tobiu closed this issue on Aug 24, 2026, 9:15 AM