Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,28 @@ pnpm open:portal # Open the portal in your browser
The first Docker start compiles the Rust AI gateway inside its development
container and can take several minutes. During development, the gateway may use
all CPUs assigned to Docker or the Podman VM, while its memory remains limited
to 2 GiB. Keep the Compose command running until the `coder-acp-copilot` logs
show that it connected to MongoDB and ensured `queue-coder-acp-copilot`; runs
submitted before that worker is ready remain pending. Later starts reuse the
gateway's named Cargo target volume and are faster.
to 2 GiB. Later starts reuse the gateway's named Cargo target volume and are
faster.

After Compose starts application services, `scripts/dev-compose.sh` verifies
their published host endpoints before printing them as usable. The Portal root,
API `/ready`, and gateway `/health/ready` must return a successful response from
the host. The Copilot worker is verified separately: its container must be
running and its logs must confirm both `queue-coder-acp-copilot` creation and
MongoDB connection. The command exits nonzero when these bounded checks expire,
so a Portal page alone is not evidence that runs can process.

Clean image builds do not consume that bounded readiness window. The wrapper
waits for Compose to create the selected application containers, while still
propagating an immediate nonzero build/start failure, and only then starts the
host endpoint and worker readiness attempts.

The failure message distinguishes an unhealthy/not-running service, a reachable
but non-ready HTTP endpoint, and a healthy container whose published port is
unreachable from the host. The last case usually points to runtime VM networking
or port forwarding. Use the printed `docker compose ps`, `docker compose port`,
and `docker compose logs` commands to inspect it; the startup script never
selects, restarts, or changes the active Docker runtime or context.

### Debug the Docker development API

Expand Down
29 changes: 29 additions & 0 deletions docs/architecture/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,35 @@ flowchart TB

## Data Flow

### Local Compose readiness boundary

Local Compose startup has two distinct readiness boundaries. Container health
proves that services can respond inside the container network; it does not prove
that a runtime VM published those ports to the developer host.

For application `up` commands, `scripts/dev-compose.sh` therefore performs
bounded host-side checks against the selected services:

| Service | Host verification contract |
|---------|----------------------------|
| Portal | Published port serves `/` with HTTP 2xx |
| API | Published port serves `/ready` with HTTP 2xx |
| AI gateway | Published port serves `/health/ready` with HTTP 2xx |
| Copilot worker | Container is running and current logs confirm queue creation plus MongoDB connection |

Only verified HTTP URLs are printed as usable. Exhausted checks fail the startup
command and distinguish container failure, host transport failure, HTTP
non-readiness, and missing worker startup signals. A healthy-container/failed-
transport result identifies runtime VM networking or port forwarding as the
likely failure class, but the wrapper only prints non-destructive inspection
commands; it never validates or changes the user's active Docker context.

The bounded readiness budget begins after Compose creates every selected
application container. Image builds remain governed by the Compose process
lifecycle instead of an independent timer: a running build can continue, a
nonzero build/start exit fails immediately, and endpoint retries cannot expire
before host publication is possible.

### User authentication

Configured human callers send the **IdP access token unchanged** on each API call.
Expand Down
161 changes: 160 additions & 1 deletion scripts/dev-compose.sh
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,163 @@ else
set -- "${FILTERED_ARGS[@]}"
fi

exec docker compose "${EXTRA_ARGS[@]}" "$@"
COMPOSE_ARGS=("${EXTRA_ARGS[@]}" "$@")

# Only `up` starts services that can be verified. Preserve direct exec behavior
# for config, build, down, and every other Compose command.
up_index=-1
for index in "${!COMPOSE_ARGS[@]}"; do
if [ "${COMPOSE_ARGS[$index]}" = "up" ]; then
up_index="$index"
break
fi
done

if [ "$up_index" -lt 0 ]; then
exec docker compose "${COMPOSE_ARGS[@]}"
fi

COMPOSE_PREFIX=("${COMPOSE_ARGS[@]:0:$up_index}")
UP_ARGS=("${COMPOSE_ARGS[@]:$((up_index + 1))}")
EXPLICIT_SERVICES=()
skip_next=0
for arg in "${UP_ARGS[@]}"; do
if [ "$skip_next" -eq 1 ]; then
skip_next=0
continue
fi
case "$arg" in
--scale|--timeout|--wait-timeout|--pull)
skip_next=1
;;
-*)
;;
*)
EXPLICIT_SERVICES+=("$arg")
;;
esac
done

VERIFY_SERVICES=""
if [ "${#EXPLICIT_SERVICES[@]}" -gt 0 ]; then
for service in "${EXPLICIT_SERVICES[@]}"; do
case "$service" in
portal|api|gateway|coder-acp-copilot)
VERIFY_SERVICES+="${VERIFY_SERVICES:+$'\n'}$service"
;;
esac
done
fi

# Explicit infra-only starts intentionally skip application readiness checks.
if [ "${#EXPLICIT_SERVICES[@]}" -gt 0 ] && [ -z "$VERIFY_SERVICES" ]; then
exec docker compose "${COMPOSE_ARGS[@]}"
fi

# Resolve profile-selected application services before startup. This lets the
# wrapper distinguish a legitimate image build (containers do not exist yet)
# from the bounded readiness phase after Compose begins creating containers.
if [ -z "$VERIFY_SERVICES" ]; then
selected_services="$(docker compose "${COMPOSE_PREFIX[@]}" config --services)"
for service in portal api gateway coder-acp-copilot; do
if grep -qx "$service" <<<"$selected_services"; then
VERIFY_SERVICES+="${VERIFY_SERVICES:+$'\n'}$service"
fi
done
fi

compose_pid=""
verify_pid=""
cleanup_children() {
local signal="$1"
if [ -n "$verify_pid" ] && kill -0 "$verify_pid" 2>/dev/null; then
kill "-$signal" "$verify_pid" 2>/dev/null || true
fi
if [ -n "$compose_pid" ] && kill -0 "$compose_pid" 2>/dev/null; then
kill "-$signal" "$compose_pid" 2>/dev/null || true
fi
}
trap 'cleanup_children INT; exit 130' INT
trap 'cleanup_children TERM; exit 143' TERM

docker compose "${COMPOSE_ARGS[@]}" &
compose_pid=$!

# Do not consume the endpoint readiness budget while Compose is still building
# images and none of the expected application containers can exist. This gate
# is tied to the Compose lifecycle rather than an arbitrary clean-build timer:
# it ends when every selected container has been created or Compose exits.
while [ -n "$compose_pid" ]; do
all_containers_created=1
while IFS= read -r service; do
[ -n "$service" ] || continue
if [ -z "$(docker compose "${COMPOSE_PREFIX[@]}" ps -q "$service" 2>/dev/null | head -n 1)" ]; then
all_containers_created=0
break
fi
done <<<"$VERIFY_SERVICES"

if [ "$all_containers_created" -eq 1 ]; then
break
fi

if ! kill -0 "$compose_pid" 2>/dev/null; then
set +e
wait "$compose_pid"
compose_status=$?
set -e
compose_pid=""
if [ "$compose_status" -ne 0 ]; then
exit "$compose_status"
fi
break
fi

sleep 0.2
done

SCOPE_DEV_VERIFY_SERVICES="$VERIFY_SERVICES" \
"$(dirname "$0")/verify-dev-stack.sh" "${COMPOSE_PREFIX[@]}" &
verify_pid=$!

# Fail fast if Compose itself exits unsuccessfully during build/startup. A
# successful detached `up -d` is allowed to exit while verification continues.
compose_status=""
while kill -0 "$verify_pid" 2>/dev/null; do
if [ -n "$compose_pid" ] && ! kill -0 "$compose_pid" 2>/dev/null; then
set +e
wait "$compose_pid"
compose_status=$?
set -e
compose_pid=""
if [ "$compose_status" -ne 0 ]; then
kill "$verify_pid" 2>/dev/null || true
wait "$verify_pid" 2>/dev/null || true
exit "$compose_status"
fi
fi
sleep 0.2
done

set +e
wait "$verify_pid"
verify_status=$?
set -e
verify_pid=""

if [ "$verify_status" -ne 0 ]; then
if [ -n "$compose_pid" ] && kill -0 "$compose_pid" 2>/dev/null; then
kill -TERM "$compose_pid" 2>/dev/null || true
wait "$compose_pid" 2>/dev/null || true
fi
exit "$verify_status"
fi

if [ -n "$compose_pid" ]; then
set +e
wait "$compose_pid"
compose_status=$?
set -e
fi

exit "${compose_status:-0}"
Loading
Loading