ADR 0026 and #13920 authorize Docker-socket runtime access only through a constrained wrapper that can prove strict service identity and exposes separate read-observe and lifecycle-write allowlists. The current wrapper allowlists service keys, but its default project binding is optional.
Live latest-open sweep: checked the latest 20 open issues at 2026-07-23T15:09:48Z; no equivalent found. The recent all-state A2A claim sweep found no overlapping lane. Semantic and exact-history sweeps surfaced #13920 and #13914 as the intended strict-identity predecessors, but current source falsifies their complete enforcement: no existing successor closes the cross-project match.
The Problem
Canonical ai/deploy/docker-compose.yml enables deployment runtime access while defaulting NEO_ORCHESTRATOR_RUNTIME_ACCESS_COMPOSE_PROJECT to empty.
DeploymentRuntimeAccessService.resolveServiceTarget() then builds a Docker query with only:
com.docker.compose.service=<serviceKey>
It adds com.docker.compose.project=<composeProject> only when a project value was configured. Multiple service-label matches fail as ambiguous, but exactly one match succeeds regardless of which Compose project owns it.
This creates two failures:
Correctness/diagnostics: a project-name mismatch can make every intended service invisible when an incorrect explicit value is set, while an empty value makes behavior depend on unrelated stacks present on the host.
Privilege-boundary breach: if the intended service is absent and one foreign Compose project has the same service label, read operations—and allowlisted lifecycle restart operations—may target that foreign container.
ADR 0026 explicitly rejects the Docker-socket option if the wrapper cannot distinguish service identity. “Unique on this host right now” is not the same as “belongs to this deployment.”
The Architectural Reality
The target identity is (composeProject, composeService), not composeService alone.
Service allowlisting and project binding solve different problems: the former bounds role/type; the latter proves deployment ownership.
DeploymentRuntimeAccessService remains the sole Docker API holder and must keep read-observe and lifecycle-write envelopes separate.
DeploymentStateBridgeService may report bounded mismatch diagnostics, but diagnostics cannot substitute for fail-closed target resolution.
AiConfig.orchestrator.runtimeAccess.composeProject remains the reactive SSOT. Consumers read the resolved leaf at use sites; no direct env reread, pass-along shadow config, hidden default, defensive optional chaining, or runtime mutation is allowed per ADR 0019.
Canonical deployment wiring must provide one deterministic project identity contract. container_name and arbitrary container enumeration are not substitutes.
Structure-map ownership: existing ai/daemons/orchestrator/services/DeploymentRuntimeAccessService.mjs, existing runtime-access AiConfig leaf and canonical Compose wiring, plus cloud deployment operations docs. No new runtime holder or actuator is required.
The Fix
Bind every runtime-access lookup to a trusted deployment project identity before enabling read or lifecycle operations.
Prefer deriving the orchestrator's own Compose project from a proven trusted runtime identity when the container/runtime makes that reliable; otherwise require an explicit configured project when runtime access is enabled. No service-label-only fallback is permitted.
If an explicit project disagrees with a proven self project identity, fail closed with a stable compose-project-mismatch diagnostic.
If no trusted/explicit project identity is available, runtime access is unavailable with a stable configuration reason; do not search for a unique service label.
Add adversarial tests in which the intended service is absent but a foreign project exposes the same service key. Neither read-observe nor lifecycle-write may address it.
Make canonical Compose and deployment docs establish one project-name contract across startup, inspection, self-heal, and redeploy tooling.
Contract Ledger Matrix
Target Surface
Source of Authority
Proposed Behavior
Fallback / Edge Case
Docs
Evidence
Runtime target identity
ADR 0026 + #13920
Resolve only (trusted project, allowlisted service)
No trusted project means runtime access unavailable
Runtime access contract
adversarial Docker API fixtures
composeProject config leaf
ADR 0019
Existing leaf is read at use sites; explicit value may bind/verify trusted identity
Explicit/self mismatch fails closed
Configuration inventory
config matrix tests
Docker query
DeploymentRuntimeAccessService
Always include project and service labels
Zero/multiple matches retain bounded failure states
Diagnostics guide
exact filter assertions
Read/lifecycle envelopes
ADR 0026
Preserve separate allowlists after project binding
Neither envelope may fall back to service-only lookup
Self-heal operations
read + restart negative tests
Deployment snapshot
DeploymentStateBridgeService
Report bounded project-binding status/reason
Never enumerate foreign container/project names
Inspection schema
redaction tests
Decision Record impact
This ticket enforces ADR 0026 rather than amending it. The ADR's Docker-socket option is valid only while strict service identity is provable; this closes the current implementation gap in that proof.
Acceptance Criteria
When runtime access is enabled, every Docker target query is bound to both an allowlisted service key and a trusted/explicit Compose project identity.
The service never accepts a unique service-label-only match for read or lifecycle operations.
If trusted self-project derivation is supported, an explicit configured mismatch fails closed with stable compose-project-mismatch evidence.
If no trusted/explicit project identity is available, runtime access reports a bounded unavailable/configuration reason and performs no service lookup or action.
An adversarial fixture with an absent intended service and one same-named service in a foreign project proves that no inspect, logs, stats, or restart request targets the foreign container.
Zero-match and multi-match states inside the correctly bound project retain stable bounded diagnostics.
Read-observe and lifecycle-write operation allowlists remain separate and unchanged in authority.
Public diagnostics do not expose arbitrary container ids, foreign project names, container names, socket details beyond the existing bounded contract, or raw Docker responses.
Canonical Compose and deployment docs define one project-identity setup/verification contract for startup, inspection, self-heal, and redeploy.
Config-touching implementation follows ADR 0019: resolved leaf at use sites, no env re-read, shadow config, hidden default, optional-chain defense, or singleton mutation.
Out of Scope
Arbitrary container discovery, shell, or exec.
Broadening service or operation allowlists.
Adding container_name as identity authority.
Redesigning the recovery controller or anti-thrash envelope.
Deployment source/version rollout authority.
Avoided Traps
Do not treat a single Docker match as ownership proof.
Do not fix only the diagnostic wording while retaining service-only actuation.
Do not duplicate the Compose project name in multiple independently mutable config paths.
Do not weaken fail-closed behavior to preserve convenience on multi-stack hosts.
Do not expose foreign project/container metadata to explain a rejected target.
Related
#13920 — runtime-access primitive whose strict-identity contract this enforces.
Context
ADR 0026 and
#13920authorize Docker-socket runtime access only through a constrained wrapper that can prove strict service identity and exposes separate read-observe and lifecycle-write allowlists. The current wrapper allowlists service keys, but its default project binding is optional.Live latest-open sweep: checked the latest 20 open issues at
2026-07-23T15:09:48Z; no equivalent found. The recent all-state A2A claim sweep found no overlapping lane. Semantic and exact-history sweeps surfaced#13920and#13914as the intended strict-identity predecessors, but current source falsifies their complete enforcement: no existing successor closes the cross-project match.The Problem
Canonical
ai/deploy/docker-compose.ymlenables deployment runtime access while defaultingNEO_ORCHESTRATOR_RUNTIME_ACCESS_COMPOSE_PROJECTto empty.DeploymentRuntimeAccessService.resolveServiceTarget()then builds a Docker query with only:It adds
com.docker.compose.project=<composeProject>only when a project value was configured. Multiple service-label matches fail as ambiguous, but exactly one match succeeds regardless of which Compose project owns it.This creates two failures:
ADR 0026 explicitly rejects the Docker-socket option if the wrapper cannot distinguish service identity. “Unique on this host right now” is not the same as “belongs to this deployment.”
The Architectural Reality
(composeProject, composeService), notcomposeServicealone.DeploymentRuntimeAccessServiceremains the sole Docker API holder and must keep read-observe and lifecycle-write envelopes separate.DeploymentStateBridgeServicemay report bounded mismatch diagnostics, but diagnostics cannot substitute for fail-closed target resolution.AiConfig.orchestrator.runtimeAccess.composeProjectremains the reactive SSOT. Consumers read the resolved leaf at use sites; no direct env reread, pass-along shadow config, hidden default, defensive optional chaining, or runtime mutation is allowed per ADR 0019.container_nameand arbitrary container enumeration are not substitutes.Structure-map ownership: existing
ai/daemons/orchestrator/services/DeploymentRuntimeAccessService.mjs, existing runtime-access AiConfig leaf and canonical Compose wiring, plus cloud deployment operations docs. No new runtime holder or actuator is required.The Fix
compose-project-mismatchdiagnostic.Contract Ledger Matrix
#13920(trusted project, allowlisted service)composeProjectconfig leafDeploymentRuntimeAccessServiceDeploymentStateBridgeServiceDecision Record impact
This ticket enforces ADR 0026 rather than amending it. The ADR's Docker-socket option is valid only while strict service identity is provable; this closes the current implementation gap in that proof.
Acceptance Criteria
compose-project-mismatchevidence.Out of Scope
container_nameas identity authority.Avoided Traps
Related
#13920— runtime-access primitive whose strict-identity contract this enforces.#13914— read-only deployment observability consumer.#14397/ PR#14399— project/label mismatch diagnostics; project naming policy remained out of scope.#15749/ PR#15754— bounded deployment bootstrap/lookup diagnostics.Origin Session ID:
fc1a49c1-e30a-4e3a-960a-e0596367a4c1Handoff Retrieval Hint:
DeploymentRuntimeAccessService compose project identity unique service label foreign container fail closed