LearnNewsExamplesServices
Frontmatter
id16206
titleai/deploy has no composable layering: the base compose cannot boot, and no configuration can prove the plane before it owns the data
stateClosed
labels
airefactoringarchitectureneeds-re-triage
assignees[]
createdAtJul 31, 2026, 2:39 AM
updatedAtAug 24, 2026, 6:08 PM
githubUrlhttps://github.com/neomjs/neo/issues/16206
authorneo-opus-vega
commentsCount8
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 24, 2026, 6:08 PM

ai/deploy has no composable layering: the base compose cannot boot, and no configuration can prove the plane before it owns the data

Closed Backlog/active-chunk-11 airefactoringarchitectureneeds-re-triage
neo-opus-vega
neo-opus-vega commented on Jul 31, 2026, 2:39 AM

Context

Surfaced empirically on 2026-07-31 while standing up a containerized MC + KB to prove the #16167 endpoint before the data move. Getting a working local, isolated, authenticated, loopback-exposed plane required hand-writing a seventh compose file in a scratchpad. That hand-written override is the finding, not the fix.

The endpoint proof itself succeeded — initialize → 200, tools/list → 42 tools on /mc/mcp and 13 on /kb/mcp through 127.0.0.1:3102 with GitHub-PAT bearer auth. The friction is that reaching that state was not expressible in ai/deploy as it stands.

Live latest-open sweep at 2026-07-31T00:55Z: no open issue covers compose layering, base bootability, or an isolated proof plane.

The Problem

1. The base compose cannot boot the servers it defines.

ai/deploy/docker-compose.yml sets NEO_AUTH_MODE zero times. It mentions the variable in two comments (:95, :179) and never assigns it. AuthService fails closed on the Streamable HTTP transport with no auth installer, so kb-server and mc-server exit 1:

Error: AuthService: no Streamable HTTP authentication installer is configured;
configure an OIDC endpoint, a built-in auth.mode, custom authMiddleware, or trusted-proxy identity
    at AuthService.setup (ai/mcp/server/shared/services/AuthService.mjs:125)

Observed, not inferred: base + an override touching only image: and the ingress port produced exactly this on both services. Fail-closed is the correct server behaviour; the defect is that the canonical topology file ships a non-bootable default while owning all 7 named volumes.

2. Auth is re-decided per overlay instead of being a fragment.

Counting real key assignments (^\s*NEO_AUTH_MODE:), with the comment-only base as the positive control:

File auth mode set loopback 3102 live-plane bind docker.sock named volumes
docker-compose.yml 0 1 7
docker-compose.dev.yml github-pat 1
docker-compose.local-agent-os.yml github-pat 2 2
docker-compose.parity-capture.yml github-pat ×2
docker-compose.parity-ci.yml local-bearer
docker-compose.test.yml 0

Five overlays, four independent auth decisions, one of them stated twice. There is no auth fragment to compose.

3. No configuration can prove the plane before it owns the data — the load-bearing item.

docker-compose.local-agent-os.yml welds four orthogonal concerns into one file: the auth profile, the loopback :3102 ingress, a bind of the live repo-root .neo-ai-data, and /var/run/docker.sock. You cannot take the loopback ingress without also taking the live plane.

The consequence shows up in the cutover runbook: ai/scripts/lifecycle/local-agent-os/README.md §5 ("Prove the replacement before resident release") runs after §3 has already mv'd the canonical plane. The only configuration that exposes a testable endpoint is the one that already owns production data, so the proof necessarily follows the irreversible step. On a machine holding ~94k memory rows that ordering is the wrong way round, and it is a property of the compose layout rather than of the runbook's author.

4. /var/run/docker.sock hardcoding — premise CORRECTED to documentation-only.

Corrected 2026-07-31 (comment, confirmed by @neo-gpt-emmy's independent container-plane evidence): the compose volume path resolves inside the Colima VM, where dockerd runs and /var/run/docker.sock exists — not on the macOS host. The orchestrator service does start as written under Colima; the live local-agent-os orchestrator container runs with the stock mount. The original "cannot start as written" claim was a host-path intuition applied to a VM-path contract.

The residual is real but documentation-only: a host-shell ls /var/run/docker.sock fails while the mount works, which is exactly the misread that cost a diagnosis round ("the Docker engine is down" — the engine was up, docker info29.2.1, context colima). The VM-vs-host path distinction belongs in the ai/deploy README.

5. Nothing tests bootability.

test/playwright/unit/ai/deploy/ParityPlaneVolumeScoping.spec.mjs gives ai/deploy coverage, but it asserts volume-scoping shape. A YAML-shape assertion cannot catch "this configuration cannot start" — which is why a non-bootable base survives a green suite.

The Architectural Reality

  • The concerns genuinely factor into four independent axes: auth mode, ingress exposure, data plane, host integration. Every existing file picks one point in that space and hardcodes it, so any unrepresented combination requires a new file. Six files today; the seventh was written tonight in a scratchpad; a contributor needing "isolated + public ingress" writes the eighth.
  • The base already models the isolated data plane correctly — 7 named volumes, no repo-root bind. That half is right and reusable; it is the auth and exposure axes that have no fragments.
  • Caddyfile.local-agent-os (loopback :8080, /mc/*mc-server:3001, /kb/*kb-server:3000) is already a clean standalone artifact and needs no change — evidence that the ingress axis separates cleanly.

The Fix

RE-SCOPED 2026-07-31 per the #16167 compliance course-correction. The immediate blocker is not a new Compose abstraction. Step 1 — unblock the migration: start the reviewed canonical Compose project with (a) stable project identity (project-name drift makes runtime service discovery see zero services; the production deployment pins it for exactly this reason), (b) Docker-owned named volumes as the data target, (c) no scratch override, (d) no bind of the pre-Docker checkout plane. The fragment-decomposition proposal below is preserved as analysis and proceeds only if still needed after the canonical relaunch — it must not compete with the compliance map's repair sequence.

Deferred decomposition sketch (post-relaunch material only):

  1. Make the base bootable or explicitly abstract. Either ship a safe default auth mode, or fail with a directed error naming which fragment to add — not a stack trace from AuthService.
  2. Extract auth fragments (github-pat, local-bearer, OIDC, trusted-proxy) so no overlay re-states a mode.
  3. Extract ingress fragments: loopback (127.0.0.1:3102) and public (443).
  4. Extract data-plane fragments: isolated named volumes, and canonical host bind.
  5. Derive the Docker socket from the active context, or move it to a host-docker fragment, so Colima and Docker Desktop both work. Corrected 2026-07-31 — documentation-only: the stock mount already works under both (in-VM path); document the VM-vs-host distinction in the ai/deploy README instead of changing code or compose.
  6. Add a bootability test that starts each supported composition far enough to prove the transport installs — an invocation, not a shape assertion.
  7. Reorder the runbook's proof once an isolated composition exists: prove on named volumes, then swap one fragment to adopt the real plane. base + auth + ingress-loopback + plane-volumes is exactly the stack that worked tonight.

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
ai/deploy/docker-compose.yml this ticket boots the transport standalone, or fails with a directed fragment-naming error never a bare AuthService stack trace ai/deploy README a test that boots base alone and asserts one of the two outcomes
auth / ingress / plane / host fragments the four observed axes each sets exactly one concern; no overlay states another's an unrepresented combination is expressible without a new bespoke file composition table in docs every existing scenario reproduced as a composition
docker socket VM-vs-host note corrected 2026-07-31: documentation-only — the stock in-VM mount works under Colima and Docker Desktop README documents that the volume path resolves in the VM, so a failing host-shell ls is not evidence of a broken mount ai/deploy README live: local-agent-os orchestrator container running under Colima with the stock mount
supported compositions the composition table each boots far enough to install its transport a broken composition fails CI, not a cutover bootability test per composition

Acceptance Criteria

  • Step 1 (re-scoped 2026-07-31, gates everything else): the reviewed canonical Compose project boots with pinned project identity and Docker-owned named volumes — no scratch override, no checkout-plane bind — and runtime discovery sees every service. Evidence: docker compose ls / labels showing the tracked config files only, plus the orchestrator's service-discovery receipt.
  • Base compose either boots the HTTP transport unaided or fails with an error naming the missing fragment. No bare AuthService trace. (Deferred: post-relaunch, only if still needed.)
  • No overlay sets NEO_AUTH_MODE; each composition selects an auth fragment. Verified by a source sweep with a positive control, since the comment-only base is exactly the case a naive grep miscounts.
  • An isolated-plane composition exists that exposes the loopback ingress without binding repo-root .neo-ai-data, and it is the documented pre-cutover proof target.
  • Every current scenario (dev, local-agent-os, parity-ci, parity-capture, test) is reproducible as a composition of fragments, each proven equivalent to today's behaviour.
  • (Corrected 2026-07-31 — documentation-only) The ai/deploy README documents the VM-vs-host docker.sock distinction: the stock mount resolves inside the Colima VM and the orchestrator starts as written; the original "cannot start under Colima / needs context-derived socket" AC is withdrawn.
  • A bootability test covers each supported composition by starting it, not by asserting YAML shape.
  • The #16167 runbook's proof step is re-sequenced to run on the isolated composition before any data move, or a successor records why it cannot be.
  • Net file count does not grow without the composition table documenting what each fragment owns.

Out of Scope

  • The #16167 cutover itself, its data move, and its wake receiver.
  • AuthService's fail-closed behaviour — correct as-is; this is about configuration not supplying it a mode.
  • Rewriting Caddyfile.local-agent-os; it already separates cleanly.
  • Kubernetes, Swarm, or any non-Compose target.

Avoided Traps

  • Adding a seventh tracked compose file. That is the symptom. Without extracted axes the next unrepresented combination just adds an eighth.
  • Counting comment mentions as settings. grep -c NEO_AUTH_MODE reports 2 for the base, both prose. The first version of the table in this ticket was wrong for exactly that reason; the AC requires a positive control.
  • Fixing the runbook's proof ordering alone. The ordering is downstream of there being no isolated composition to prove against; reordering prose without adding one changes nothing.
  • Treating the Colima socket as a local quirk. It is a hardcoded host assumption in a file that also ships to cloud. Corrected 2026-07-31: the "hardcoded host assumption" premise was itself the trap — the path is a VM-side contract that works under both engines. The durable lesson is the inverse: verify a mount path in the namespace that consumes it before calling it broken.
  • A shape-only bootability test. Asserting an auth key exists is not proof a transport installs — the defect being fixed here is precisely one a shape assertion missed.

Decision Record impact

aligned-with ADR 0014 (deployment topology and the container-plane / host-edge split — this makes its intended configurations expressible rather than changing them). No ADR is amended. If the fragment decomposition proves to have wider blast radius than one lane, this graduates to a Discussion before implementation.

Related

  • #16167 — the cutover whose §5 proof ordering this explains; the endpoint proof that surfaced all of it ran during that lane
  • #16205 — provisioning templates still emitting the retired stdio shape
  • #16197 — orchestrator lanes scheduled without regard to the authority profile; same family of "configuration says one thing, runtime does another"
  • ADR 0014, ADR 0019

Origin Session ID: 0a7f5f1d-cf12-4698-984c-17b64eea5178

Retrieval Hint: ai/deploy compose layering fragments base not bootable NEO_AUTH_MODE unset AuthService fail closed isolated plane proof before data move docker.sock colima VM path corrected bootability test

tobiu referenced in commit a249882 - "fix(ai): restore canonical local Compose target (#16206) (#16218) on Jul 31, 2026, 7:59 AM