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
39 changes: 27 additions & 12 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,29 @@ on:

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build:
name: Build (${{ matrix.platform }})
name: Build ${{ matrix.component.name }} (${{ matrix.platform }})
runs-on: ${{ matrix.runner }}
permissions:
contents: read
packages: write
strategy:
matrix:
component:
- name: inputlayer
target: engine
- name: inputlayer-gateway
target: gateway
platform: [linux/amd64, linux/arm64]
include:
- platform: linux/amd64
runner: blacksmith-4vcpu-ubuntu-2404
arch: amd64
- platform: linux/arm64
runner: blacksmith-4vcpu-ubuntu-2404-arm
arch: arm64
steps:
- uses: actions/checkout@v4

Expand All @@ -43,18 +50,19 @@ jobs:
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
images: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ matrix.component.name }}

- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
context: .
target: ${{ matrix.component.target }}
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }}
cache-from: type=gha,scope=${{ matrix.platform }}
cache-to: type=gha,scope=${{ matrix.platform }},mode=max
outputs: type=image,name=${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ matrix.component.name }},push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }}
cache-from: type=gha,scope=${{ matrix.component.target }}-${{ matrix.platform }}
cache-to: type=gha,scope=${{ matrix.component.target }}-${{ matrix.platform }},mode=max

- name: Export digest
if: github.event_name != 'pull_request'
Expand All @@ -67,25 +75,32 @@ jobs:
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v4
with:
name: digests-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
name: digests-${{ matrix.component.target }}-${{ matrix.arch }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1

merge:
name: Merge manifests
name: Merge manifests (${{ matrix.component.name }})
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
needs: build
permissions:
contents: read
packages: write
strategy:
matrix:
component:
- name: inputlayer
target: engine
- name: inputlayer-gateway
target: gateway
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-*
pattern: digests-${{ matrix.component.target }}-*
merge-multiple: true

- name: Set up Docker Buildx
Expand All @@ -102,7 +117,7 @@ jobs:
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
images: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ matrix.component.name }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=ref,event=branch
Expand All @@ -114,8 +129,8 @@ jobs:
working-directory: /tmp/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
$(printf '${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ matrix.component.name }}@sha256:%s ' *)

- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ matrix.component.name }}:${{ steps.meta.outputs.version }}
4 changes: 3 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ jobs:
- name: Publish to crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish || echo "Publish failed (version may already exist)"
run: |
cargo publish -p inputlayer-ontology-client || echo "inputlayer-ontology-client publish failed (version may already exist)"
cargo publish -p inputlayer || echo "inputlayer publish failed (version may already exist)"

# Create GitHub release
github-release:
Expand Down
12 changes: 11 additions & 1 deletion Caddyfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
{$DOMAIN:localhost} {
reverse_proxy inputlayer:8080
# Verified Completions API (the model gateway)
handle /v1/verify* {
reverse_proxy gateway:8081
}
handle /v1/chat/completions* {
reverse_proxy gateway:8081
}
# Everything else: the engine (WS API, GUI, health, metrics)
handle {
reverse_proxy inputlayer:8080
}
}
7 changes: 3 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# per #82); the gateway is a separate member so HTTP/LLM client dependencies
# never enter the engine crate.
[workspace]
members = ["gateway"]
members = ["gateway", "ontology-client"]

[package]
name = "inputlayer"
Expand Down Expand Up @@ -92,9 +92,8 @@ tokio-tungstenite = "0.24"
clap = { version = "4.5.60", features = ["derive", "env"] }
crc32fast = "1.5.0"

# Ontology registry client (il CLI)
tar = "0.4"
flate2 = "1.0"
# Shared registry + WS client (il CLI, gateway)
inputlayer-ontology-client = { path = "ontology-client", version = "0.1.0" }

[dev-dependencies]
tempfile = "3.0"
Expand Down
63 changes: 50 additions & 13 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,70 @@ COPY gui/ ./gui/
COPY docs/content/ ./docs/content/
RUN cd gui && npm run build

# ---- Rust Builder ----
# ---- Rust Builder (shared by both images) ----
FROM rust:1.88-bookworm AS builder

WORKDIR /build

# Cache dependencies: copy manifests first, build a dummy lib to cache deps.
# gateway/ is a workspace member, so its manifest and a stub source must
# exist for any cargo invocation to load the workspace.
# Cache dependencies: copy manifests first, build dummy targets to cache deps
COPY Cargo.toml ./
COPY gateway/Cargo.toml ./gateway/
COPY ontology-client/Cargo.toml ./ontology-client/
RUN mkdir src && echo "fn main() {}" > src/main.rs && \
mkdir -p src/bin && echo "fn main() {}" > src/bin/server.rs && \
echo "" > src/lib.rs && \
mkdir -p gateway/src && echo "" > gateway/src/lib.rs && \
mkdir -p gateway/src && echo "fn main() {}" > gateway/src/main.rs && \
echo "" > gateway/src/lib.rs && \
mkdir -p ontology-client/src && echo "" > ontology-client/src/lib.rs && \
cargo generate-lockfile && \
cargo build --release --bin inputlayer-server 2>/dev/null || true && \
rm -rf src gateway/src

# Build the real binary
(cargo build --release -p inputlayer --bin inputlayer-server && \
cargo build --release -p inputlayer-gateway --bin inputlayer-gateway) 2>/dev/null || true && \
rm -rf src gateway/src ontology-client/src

# Build the real binaries. The touch is load-bearing: COPY preserves context
# mtimes, which in CI predate the dummy dep-cache build above - without it
# cargo considers the crates fresh and ships the dummy `fn main() {}`
# binaries (the engine only escaped via the --all-features fingerprint
# change; the featureless gateway crate has no such protection).
COPY src/ src/
COPY gateway/ gateway/
COPY ontology-client/ ontology-client/
COPY docs/ docs/
RUN cargo build --all-features --release --bin inputlayer-server && \
strip target/release/inputlayer-server
RUN find src gateway/src ontology-client/src -type f -exec touch {} + && \
cargo build --all-features --release -p inputlayer --bin inputlayer-server && \
cargo build --release -p inputlayer-gateway --bin inputlayer-gateway && \
strip target/release/inputlayer-server target/release/inputlayer-gateway

# ---- Gateway Runtime (build with: docker build --target gateway) ----
FROM debian:bookworm-slim AS gateway

RUN apt-get update && \
apt-get install -y --no-install-recommends ca-certificates curl && \
rm -rf /var/lib/apt/lists/*

RUN useradd -r -s /bin/false -m -d /var/lib/inputlayer gateway

COPY --from=builder /build/target/release/inputlayer-gateway /usr/local/bin/

# Gateway configuration is its own surface, separate from the engine:
# GATEWAY_HOST / GATEWAY_PORT bind address
# INPUTLAYER_URL engine base URL
# INPUTLAYER_API_KEY engine API key (WS access, from #83 on)
# ANTHROPIC_API_KEY model provider key (never seen by the engine)
ENV GATEWAY_HOST=0.0.0.0
ENV GATEWAY_PORT=8081
ENV INPUTLAYER_URL=http://inputlayer:8080

EXPOSE 8081
USER gateway

HEALTHCHECK --interval=10s --timeout=3s --start-period=5s --retries=3 \
CMD curl -sf "http://localhost:${GATEWAY_PORT:-8081}/health" || exit 1

ENTRYPOINT ["inputlayer-gateway"]

# ---- Runtime ----
FROM debian:bookworm-slim
# ---- Engine Runtime (default target, keep last) ----
FROM debian:bookworm-slim AS engine

RUN apt-get update && \
apt-get install -y --no-install-recommends ca-certificates curl && \
Expand Down
28 changes: 14 additions & 14 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ test-all: check static-analysis
echo ""; \
rm -f /tmp/il_trace.log /tmp/il_server.log 2>/dev/null || true; \
echo "=== Build (release) ==="; \
if cargo build --all-features --release 2>&1; then \
if cargo build --workspace --all-features --release 2>&1; then \
BUILD_STATUS="PASS"; \
else \
BUILD_STATUS="FAIL"; \
Expand All @@ -42,7 +42,7 @@ test-all: check static-analysis
echo "=== Unit + Integration Tests ($$NCPU threads) ==="; \
UNIT_TMPFILE=$$(mktemp); \
set -o pipefail; \
RUST_TEST_THREADS=$$NCPU cargo test --all-features -- --test-threads=$$NCPU --format=pretty \
RUST_TEST_THREADS=$$NCPU cargo test --workspace --all-features -- --test-threads=$$NCPU --format=pretty \
2>&1 | tee "$$UNIT_TMPFILE"; \
UNIT_EXIT=$${PIPESTATUS[0]}; \
tail -5 "$$UNIT_TMPFILE"; \
Expand All @@ -67,7 +67,7 @@ test-all: check static-analysis
echo "=== Snapshot Tests (E2E) ==="; \
SNAP_TMPFILE=$$(mktemp); \
set -o pipefail; \
IL_TRACE=1 IL_TRACE_LEVEL=trace IL_SERVER_LOG=/tmp/il_server.log IL_TRACE_FILE=/tmp/il_trace.log \
INPUTLAYER_TRACE=1 INPUTLAYER_TRACE_LEVEL=trace INPUTLAYER_SERVER_LOG=/tmp/il_server.log INPUTLAYER_TRACE_FILE=/tmp/il_trace.log \
./scripts/run_snapshot_tests.sh --skip-build 2>&1 | tee "$$SNAP_TMPFILE"; \
SNAP_EXIT=$${PIPESTATUS[0]}; \
tail -10 "$$SNAP_TMPFILE"; \
Expand Down Expand Up @@ -187,7 +187,7 @@ ci-test-all:
echo ""; \
echo "=== Build (release, thin LTO) ==="; \
if CARGO_PROFILE_RELEASE_LTO=thin CARGO_PROFILE_RELEASE_CODEGEN_UNITS=4 \
cargo build --all-features --release 2>&1; then \
cargo build --workspace --all-features --release 2>&1; then \
BUILD_STATUS="PASS"; \
else \
BUILD_STATUS="FAIL"; \
Expand All @@ -197,7 +197,7 @@ ci-test-all:
echo "=== Unit Tests ==="; \
UNIT_TMPFILE=$$(mktemp); \
set -o pipefail; \
RUST_TEST_THREADS=$$CI_JOBS cargo test --all-features -- --test-threads=$$CI_JOBS --format=pretty \
RUST_TEST_THREADS=$$CI_JOBS cargo test --workspace --all-features -- --test-threads=$$CI_JOBS --format=pretty \
2>&1 | tee "$$UNIT_TMPFILE"; \
UNIT_EXIT=$${PIPESTATUS[0]}; \
tail -5 "$$UNIT_TMPFILE"; \
Expand Down Expand Up @@ -285,11 +285,11 @@ ci: check ci-test-all

# Tier 1: Unit tests (cargo test - includes all #[test] functions)
unit-test:
cargo test --all-features
cargo test --workspace --all-features

# Tier 2: Integration tests only
integration-test:
cargo test --all-features --test '*'
cargo test --workspace --all-features --test '*'

# Tier 3: E2E snapshot tests (parallel, against live server)
e2e-test:
Expand Down Expand Up @@ -322,7 +322,7 @@ python-test:
# Build without gui-build so CI doesn't need Node.js. The server runs
# headless for integration tests.
python-test-live:
@cargo build --all-features --release
@cargo build --workspace --all-features --release
@DATA_DIR=$$(mktemp -d -t il-py-live-XXXXXX); \
rm -rf $$DATA_DIR && mkdir -p $$DATA_DIR; \
echo "[python-test-live] data dir: $$DATA_DIR"; \
Expand Down Expand Up @@ -358,7 +358,7 @@ python-test-live:
# an LLM (1, 2, 3) end-to-end against a live server. Catches example
# regressions that the unit tests cannot.
python-test-examples:
@cargo build --all-features --release
@cargo build --workspace --all-features --release
@DATA_DIR=$$(mktemp -d -t il-py-ex-XXXXXX); \
rm -rf $$DATA_DIR && mkdir -p $$DATA_DIR; \
lsof -ti :8080 | xargs -r kill -9 2>/dev/null || true; \
Expand Down Expand Up @@ -474,11 +474,11 @@ fmt-check:

# Run clippy lints
lint:
cargo clippy --all-features -- -D warnings
cargo clippy --workspace --all-features -- -D warnings

# Check compilation + formatting + lints (quality gate)
check: fmt-check lint
cargo check --all-features
cargo check --workspace --all-features

# Fix formatting and lint issues automatically where possible
fix: fmt
Expand All @@ -492,11 +492,11 @@ build: gui-build

# Build in release mode (GUI + Rust)
build-release: gui-build
cargo build --all-features --release
cargo build --workspace --all-features --release

# Run unit tests in release mode
test-release:
cargo test --all-features --release
cargo test --workspace --all-features --release

# Clean build artifacts
clean:
Expand All @@ -511,7 +511,7 @@ doc:

# Check documentation (CI mode)
doc-check:
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --all-features

# Release & Maintenance

Expand Down
6 changes: 3 additions & 3 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ Environment variables:
Enable structured server tracing logs (useful for diagnosing hangs/timeouts):

```bash
IL_TRACE=1 IL_TRACE_FILE=/tmp/inputlayer-trace.log ./scripts/run_snapshot_tests.sh
INPUTLAYER_TRACE=1 INPUTLAYER_TRACE_FILE=/tmp/inputlayer-trace.log ./scripts/run_snapshot_tests.sh
```

Optional:
| Variable | Default | Description |
|----------|---------|-------------|
| `IL_TRACE_JSON` | 0 | Set to `1` for JSON logs |
| `IL_TRACE_LEVEL` | `trace` | Log level (e.g., `info`, `debug`, `trace`) |
| `INPUTLAYER_TRACE_JSON` | 0 | Set to `1` for JSON logs |
| `INPUTLAYER_TRACE_LEVEL` | `trace` | Log level (e.g., `info`, `debug`, `trace`) |

### Affected-Only Tests

Expand Down
Loading
Loading