Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .github/workflows/build-embeddings.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
name: Build Offline Embedding Pipeline

on:
schedule:
- cron: "0 9 * * 0"
workflow_dispatch:

permissions: read-all
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/build-mcp-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ jobs:
context: .
file: mcp-local/Dockerfile
platforms: ${{ matrix.platform }}
network: none
build-args: |
UBUNTU_IMAGE=${{ steps.locked_images.outputs.ubuntu_image }}
EMBEDDINGS_IMAGE=${{ steps.locked_images.outputs.embeddings_image }}
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ jobs:
run: |
docker buildx build \
--platform ${{ matrix.platform }} \
--network none \
-f mcp-local/Dockerfile \
-t arm-mcp:latest \
--output type=docker \
Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ embedding images are private:
```bash
docker login ghcr.io
docker buildx build \
--network none \
--file mcp-local/Dockerfile \
--tag arm-mcp:local \
--load \
Expand Down Expand Up @@ -426,7 +427,8 @@ Production must never consume a mutable tag.
Embedding updates use an automated promotion PR instead of being copied into
the MCP release directly:

1. Manually run **Build Offline Embedding Pipeline** from `main`.
1. Let **Build Offline Embedding Pipeline** run from `main` every Sunday at
09:00 UTC, or start it manually for an out-of-band update.
2. The workflow publishes an immutable candidate vector-store image and opens
or updates `automation/pin-embedding-vectorstore`.
3. The promotion branch updates both `container_images.embeddings` in
Expand Down Expand Up @@ -469,6 +471,15 @@ The publication workflow also creates a tag containing the source commit,
workflow run ID, and attempt. That tag is only a discovery aid; production
builds always use the digest.

#### Rolling Back an Input Update

Rollback is a reviewed pin change. Restore the last approved image references
and metadata in `mcp-local/build-inputs.lock.json`, and keep the corresponding
image defaults in `mcp-local/Dockerfile` synchronized. Submit the rollback
through the normal pull-request process and run the AMD64 and Arm64 integration
builds before release. Do not delete, overwrite, or retag the immutable GHCR
artifacts.

## Troubleshooting

### Accessing the Container Shell
Expand Down
3 changes: 2 additions & 1 deletion embedding-generation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ one immutable artifact. It is published privately as
The embedding pipeline publishes candidates; it does not cause the MCP image
to consume the newest registry artifact automatically. To promote a candidate:

1. Run **Build Offline Embedding Pipeline** from `main`.
1. Let **Build Offline Embedding Pipeline** run from `main` every Sunday at
09:00 UTC, or start it manually for an out-of-band update.
Comment thread
brikin01 marked this conversation as resolved.
2. After publishing the vector store, the workflow opens or updates the
`automation/pin-embedding-vectorstore` PR with the immutable digest in both
`mcp-local/build-inputs.lock.json` and `mcp-local/Dockerfile`.
Expand Down
6 changes: 3 additions & 3 deletions mcp-local/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ RUN --network=none apt-get install -y --no-download --no-install-recommends \
rm -rf /var/lib/apt/lists/*

WORKDIR /app
RUN python3 -m venv "$VIRTUAL_ENV"
RUN --network=none python3 -m venv "$VIRTUAL_ENV"

# The requirements file and wheelhouse come from the same immutable input
# bundle. Network access is disabled for installation.
Expand Down Expand Up @@ -81,7 +81,7 @@ RUN --network=none mkdir -p /opt/ArmPerformix-cli-current && \
# Keep the model, metadata, and index from the same immutable vector-store
# artifact. The model is architecture-independent even though generation ran
# on arm64.
RUN mkdir -p /app/data
RUN --network=none mkdir -p /app/data
COPY --from=embeddings /embedding-data/embedding-model/ /app/embedding-model/
COPY --from=embeddings /embedding-data/metadata.json /app/data/metadata.json
COPY --from=embeddings /embedding-data/usearch_index.bin /app/data/usearch_index.bin
Expand Down Expand Up @@ -122,7 +122,7 @@ COPY --from=builder /opt/arm-migration-tools/migrate-ease /opt/arm-migration-too
COPY --from=builder /usr/local/bin/migrate-ease-* /usr/local/bin/
COPY --from=builder /app /app

RUN ln -sf "${APX_BIN}" /usr/local/bin/apx
RUN --network=none ln -sf "${APX_BIN}" /usr/local/bin/apx

WORKDIR /app

Expand Down
25 changes: 15 additions & 10 deletions mcp-local/tests/test_build_inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,21 @@ def test_final_builds_do_not_acquire_inputs_live() -> None:
assert "python -m pip install --upgrade pip" not in INTEGRATION_WORKFLOW


def test_final_builds_disable_network_for_every_run_instruction() -> None:
run_instructions = [
line.strip()
for line in DOCKERFILE.splitlines()
if re.match(r"(?i:RUN)(?:\s|$)", line.strip())
]
assert run_instructions
assert all(
re.match(r"(?i:RUN)\s+--network=none(?:\s|$)", instruction)
for instruction in run_instructions
)
Comment thread
Copilot marked this conversation as resolved.
assert " network: none\n" in IMAGE_WORKFLOW
assert " --network none \\\n" in INTEGRATION_WORKFLOW


def test_release_build_loads_image_arguments_from_manifest() -> None:
assert 'lock_file="mcp-local/build-inputs.lock.json"' in IMAGE_WORKFLOW
assert "@sha256:[0-9a-f]{64}" in IMAGE_WORKFLOW
Expand Down Expand Up @@ -272,13 +287,3 @@ def test_input_publication_is_manual_private_and_multi_architecture() -> None:
assert '"export",' in STAGE_INPUTS
assert 'output / "requirements.lock"' in STAGE_INPUTS
assert 'echo "- MCP build input: \\`${IMAGE}@${digest}\\`"' in INPUT_WORKFLOW


def test_input_publication_uses_pinned_build_actions() -> None:
action_lines = [
line.strip()
for line in INPUT_WORKFLOW.splitlines()
if line.strip().startswith("uses:")
]
assert action_lines
assert all(re.fullmatch(r"uses: [^@]+@[0-9a-f]{40}(?: # .+)?", line) for line in action_lines)