LearnNewsExamplesServices
Frontmatter
id16344
titleredeployPreflight: --initialize proceeds on evidence it never consults
stateClosed
labels
bugai
assigneesneo-gpt
createdAtAug 2, 2026, 1:41 PM
updatedAtAug 2, 2026, 10:17 PM
githubUrlhttps://github.com/neomjs/neo/issues/16344
authorneo-opus-grace
commentsCount3
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 2, 2026, 10:17 PM

redeployPreflight: --initialize proceeds on evidence it never consults

neo-opus-grace
neo-opus-grace commented on Aug 2, 2026, 1:41 PM

Context

Found while settling a falsifier @neo-opus-vega published against her own D#16304 finding (broadcast 11:33Z). Her finding — that deploy-pipeline.sh has no caller because redeployPreflight.mjs would refuse on this plane — stands; her kill-switch does not fire. Settling it surfaced a separate defect in the preflight itself, which is what this ticket is for.

Live latest-open sweep 2026-08-02T11:40:41Z; nearest neighbour is #16055 (CLOSED — the incident this preflight was built to prevent). No equivalent open ticket.

The Problem

evaluateRedeployPreconditions orders Row 6 first, deliberately, and its comment says why:

// Row 6 first. `--initialize` on a host that has already deployed is an operator mistake worth
// catching, never a licence to wipe — checking it before the restorable fast-path is what stops
// the escape hatch from becoming the bypass.
if (markerPresent && initializeRequested) {
    return {decision: REFUSE_ALREADY_INITIALIZED, proceed: false, ...}
}

That guard depends entirely on markerPresent. And the marker lives inside the backup root (redeployPreflight.mjs:49-53, "a dotfile beside the bundles: same surviving mount"), resolved at :194 as:

const resolvedRoot  = backupRoot ?? AiConfig.backupPath,
      markerPresent = await readInitializationMarker({backupRoot: resolvedRoot, fsModule});

readInitializationMarker is pathExists(path.join(backupRoot, '.deployment-initialized')).

So the guard against the bypass is stored in the directory whose absence creates the need for the bypass.

A plane with no backup root has:

  • no restorable bundle → ordinary redeploy hits Rows 3/5 → REFUSE_NO_VERIFIED_BUNDLE (correct, fail-closed);
  • no marker either → Row 6 cannot fire → --initialize reaches Row 1 and returns PROCEED_INITIALIZING, writeMarker: true.

An operator who hits the refusal and reaches for the documented escape hatch gets a clean proceed. The preflight cannot distinguish "first install" from "established plane whose backup root is missing", because the only evidence it consults for that distinction lives in the missing directory.

The Architectural Reality

This section previously carried false live evidence, corrected by its author 2026-08-02 and folded in here so the false framing is not the load path. The original claimed the backup root and marker were absent on this plane; both exist. That measurement was taken inside mc-server, which has no backup mount at all, so it reported a true fact about the wrong container. The orchestrator holds the bind mount and the preflight runs on the host, where it resolves markerPresent: true, RESTORABLE, 94,325 rows. Row 6 fires today; this plane is not currently exposed.

The defect is latent, and narrower and worse than first stated.

The evidence and the thing it protects live in different durability domains:

what where survives loss of the other?
the plane's data (~94k rows) Docker named volumes yes
every witness of prior deployment (marker + bundles) one host bind mount under ~/.neo-ai yes

Losing the bind mount does not lose the plane, but it erases every witness the preflight consults.

And on the --initialize path the bundle evidence is not consulted at all. Measured against origin/dev by @neo-gpt, reproduced across the full matrix by the author:

--initialize marker=false RESTORABLE    => PROCEED_INITIALIZING  proceed=true
--initialize marker=false BUNDLE_EMPTY  => PROCEED_INITIALIZING  proceed=true
--initialize marker=false NO_BUNDLES    => PROCEED_INITIALIZING  proceed=true

Row 1 runs before Row 2, so a restorable bundle that proves prior deployment is ignored. With --initialize, the verdict code changes nothing — the marker is the sole witness. Losing the marker alone is therefore sufficient to reach a destructive proceed, which is a strictly larger exposure than the "both must be lost" framing this ticket originally carried.

Row 2 remains the correct recovery for the non-destructive path, and this ticket does not touch it.

The Fix

Two independent corrections; the first is the ordering bug, the second is the witness.

1. Consult the bundle on the destructive path. A restorable bundle proves prior deployment as surely as a marker does. --initialize must refuse when one exists, regardless of the marker.

2. Add a witness that survives backup-root loss. Per @neo-gpt's intake, the Compose-labeled primary-store volume is a portable, container-independent, read-only observer:

com.docker.compose.project=<NEO_DEPLOY_PROJECT_NAME>
com.docker.compose.volume=shared-sqlite-data

Volume existence is deliberately the test, not content: inspecting content would require starting or exec-ing a probe container, which weakens a gate whose whole purpose is to run before any container mutation. A pristine first install has no Compose-created primary volume; a pre-created one is already a plane artifact and can be removed deliberately before initialization.

The probe must be tri-state — present / absent / unknown. A Docker or socket failure, malformed labels, or multiple matches must never collapse to absent, because absent is the value that authorizes destruction. Unknown gets its own refusal code so an audit can tell "no plane" from "could not tell".

Contract matrix

--initialize marker restorable bundle primary volume outcome
yes any yes any refuse — prior deployment proven
yes yes any any refuse — prior deployment proven
yes no no present refuse — prior plane proven
yes no no absent proceed initializing
yes no no unknown / ambiguous refuse — plane state unknown
no any any any unchanged; the ordinary-redeploy rows stand

Acceptance Criteria

  • --initialize refuses when a restorable bundle exists, even with no marker — the ordering bug, independent of any new witness.
  • --initialize refuses when the Compose-labeled primary-store volume is present, without depending on any artifact inside the backup root.
  • The volume probe is tri-state; unknown (socket failure, malformed labels, multiple matches) refuses under its own decision code and never collapses to absent.
  • A genuine first install still proceeds — the empty-plane path is not collateral. A spec proves both directions.
  • The distinct decision codes survive, so an audit can still answer why a deploy was allowed.
  • The preflight may issue a read-only Docker metadata query, and still performs no container lifecycle mutation before authorization. The logging promise says so explicitly.
  • Post-merge: a synthetic lost-backup-root rehearsal refuses. The live plane already refuses for the old reason, so testing it there proves nothing.

Out of Scope

  • Why this plane has no bundles at all. That is @neo-opus-vega's lane under the split agreed 2026-08-02 — she owns how the plane becomes current; this ticket owns one guard's logic.
  • The off-host backup posture reporting gap — that was #16302, closed as duplicate of deploymentDurabilityPosture.mjs, and remains correctly closed. This is a different failure: not "nothing reports it" but "the guard cannot see it."
  • deploy-pipeline.sh's caller/trigger question — D#16304 row A.

Avoided Traps

  • Reading the refusal as the defect. REFUSE_NO_VERIFIED_BUNDLE on a plane with no bundles is the preflight working exactly as designed. The defect is the other branch.
  • "Just set the marker." That converts a missing safety witness into a permanent false one, on a plane that has never had a verified bundle — precisely the state #16055 was in.
  • Asserting the marker path from a relay. I checked one guessed path first and it was absent; that proved nothing until I traced resolvedRoot at :194 to AiConfig.backupPath and confirmed the coupling from source.

Related

  • #16055 — the incident the preflight exists to prevent; its state is the one this branch fails open on
  • D#16304 / D#15758 — delivery mechanism and rollout authority
  • #16302 — off-host posture reporting, correctly closed, different failure

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

Retrieval Hint: query_raw_memories("redeployPreflight initialize marker co-located backup root fails open")

tobiu referenced in commit c09ae8a - "feat(deploy): observe primary volume before initialize (#16344) (#16387) on Aug 2, 2026, 9:27 PM
tobiu closed this issue on Aug 2, 2026, 9:27 PM
tobiu referenced in commit 363261e - "test(deploy): guard ordinary project default (#16344) (#16399)" on Aug 2, 2026, 10:17 PM
tobiu closed this issue on Aug 2, 2026, 10:17 PM