diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..cc0369d
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,13 @@
+# Copy this file to .env and fill in your values before running docker-compose.native.yml
+# .env is gitignored — never commit real credentials
+
+# OpenAI
+OPENAI_API_KEY=sk-...
+
+# AWS S3 (figure storage — leave blank if using local filesystem)
+AWS_ACCESS_KEY_ID=
+AWS_SECRET_ACCESS_KEY=
+AWS_REGION=eu-west-1
+
+# S3 bucket name (if S3 storage enabled)
+APP_STORAGE_S3_BUCKET=ai-teacher-figures
diff --git a/CLAUDE.md b/CLAUDE.md
index 28ba62c..a29d9b6 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -14,6 +14,8 @@ Auto-generated from all feature plans. Last updated: 2026-04-07
- PostgreSQL (sections, figures, messages — unchanged). No new tables needed. (004-rag-retrieval-quality)
- Java 21 (backend), TypeScript / Node 20 (frontend) + Spring Boot 4.0.5, Spring AI 2.0.0-M4, OpenAI API (chat + embeddings), Vue 3.4, Pinia 2.1, Axios 1.7 (004-rag-retrieval-quality)
- PostgreSQL (JPA + Flyway), pgvector (`VectorStore`) (004-rag-retrieval-quality)
+- Java 25 (backend), TypeScript / Node 20 (frontend) + Spring Boot 4.0.5, Spring AI 2.0.0-M4, `native-maven-plugin` 0.10.6, (005-native-image-deployment)
+- PostgreSQL 16 + pgvector (unchanged) (005-native-image-deployment)
- Java 21 (backend), TypeScript / Node 20 (frontend) (001-neuro-rag-learning)
@@ -33,9 +35,9 @@ npm test && npm run lint
Java 21 (backend), TypeScript / Node 20 (frontend): Follow standard conventions
## Recent Changes
+- 005-native-image-deployment: Added Java 25 (backend), TypeScript / Node 20 (frontend) + Spring Boot 4.0.5, Spring AI 2.0.0-M4, `native-maven-plugin` 0.10.6,
- 004-rag-retrieval-quality: Added Java 21 (backend), TypeScript / Node 20 (frontend) + Spring Boot 4.0.5, Spring AI 2.0.0-M4, OpenAI API (chat + embeddings), Vue 3.4, Pinia 2.1, Axios 1.7
- 004-rag-retrieval-quality: Added Java 21 (backend), TypeScript / Node 20 (frontend) + Spring Boot 4.0.5, Spring AI 2.0.0-M4, OpenAI API (chat + embeddings), pgvector, Vue 3.4, Pinia 2.1
-- 003-basic-login: Added Java 21 (backend) / TypeScript + Node 20 (frontend) + Spring Boot 4.0.5, Spring Security (already included), Vue 3.4, Vue Router 4.3, Pinia 2.1, Axios 1.7
diff --git a/README.md b/README.md
index 8ef2994..8c6aaca 100644
--- a/README.md
+++ b/README.md
@@ -137,7 +137,7 @@ PictureGroup
## Stack
-- **Backend**: Spring Boot 4.0.5 + Spring AI 2.0.0-M4, Java 21, Maven
+- **Backend**: Spring Boot 4.0.5 + Spring AI 2.0.0-M4, Java 25, Maven
- **Frontend**: Vue.js 3 + Vite + TypeScript + Pinia + Axios
- **Database**: PostgreSQL 16 + pgvector extension
- **Auth**: HTTP Basic (single shared in-memory user)
@@ -146,7 +146,7 @@ PictureGroup
See [specs/001-neuro-rag-learning/quickstart.md](specs/001-neuro-rag-learning/quickstart.md) for full instructions.
-### Local Dev
+### Local Dev (JVM)
```bash
# Start the database
@@ -162,6 +162,55 @@ npm install
npm run dev
```
+### Native Image Build
+
+Produces a GraalVM native binary packaged into a minimal Docker image via Jib.
+
+**Prerequisite**: GraalVM 25 must be installed and set as `JAVA_HOME`.
+
+```bash
+# Install GraalVM 25 CE via sdkman (one-time)
+sdk install java 25-graalce
+sdk use java 25-graalce
+
+# Build native executable + Docker image (requires Docker daemon)
+cd backend
+mvn -Pnative package jib:dockerBuild -DskipTests
+```
+
+The image `ai-teacher-backend:latest` will appear in your local Docker. It starts in under 1 second and uses significantly less memory than the JVM image.
+
+### Run Native Stack (Docker Compose)
+
+```bash
+# Copy and fill in secrets
+cp .env.example .env
+# edit .env — add OPENAI_API_KEY at minimum
+
+# Start PostgreSQL + native backend
+docker compose -f docker-compose.native.yml up
+```
+
+App available at `http://localhost:8080`.
+
+### Build Pipeline (Native)
+
+```mermaid
+graph LR
+ SRC["Source Code\n(Java 25)"]
+ AOT["Spring Boot AOT\n(process-aot)"]
+ NI["GraalVM native-image\n(native-maven-plugin)"]
+ EXE["Native Executable\ntarget/ai-teacher-backend"]
+ JIB["Jib\n(jib-native-image-extension)"]
+ IMG["Docker Image\nai-teacher-backend:latest\n(distroless base)"]
+
+ SRC --> AOT
+ AOT --> NI
+ NI --> EXE
+ EXE --> JIB
+ JIB --> IMG
+```
+
### Environment Variables
#### Backend
diff --git a/backend/.dockerignore b/backend/.dockerignore
new file mode 100644
index 0000000..f666d2d
--- /dev/null
+++ b/backend/.dockerignore
@@ -0,0 +1,24 @@
+# Java build artifacts
+target/
+*.class
+*.jar
+
+# Git
+.git/
+.gitignore
+
+# Editor
+.vscode/
+.idea/
+*.iml
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Logs
+*.log
+
+# Environment
+.env
+.env.*
diff --git a/backend/Dockerfile.native b/backend/Dockerfile.native
new file mode 100644
index 0000000..fddf7e9
--- /dev/null
+++ b/backend/Dockerfile.native
@@ -0,0 +1,26 @@
+# ---- Pull Maven from its official image (avoids microdnf under QEMU) ----
+FROM maven:3.9.9-eclipse-temurin-21 AS maven-dist
+
+# ---- Build stage: GraalVM 25 + Maven ----
+ARG TARGETPLATFORM=linux/arm64
+FROM --platform=$TARGETPLATFORM ghcr.io/graalvm/native-image-community:25 AS build
+
+# Copy Maven from the official Maven image — no package installation needed
+COPY --from=maven-dist /usr/share/maven /opt/maven
+ENV PATH="/opt/maven/bin:$PATH"
+
+WORKDIR /app
+
+# Cache dependency resolution separately from source compilation
+COPY pom.xml .
+RUN mvn -Pnative dependency:resolve dependency:resolve-plugins -q
+
+# Build native executable
+COPY src ./src
+RUN mvn -Pnative package -DskipTests
+
+# ---- Runtime stage: minimal ARM64 distroless ----
+FROM --platform=$TARGETPLATFORM gcr.io/distroless/base-nossl-debian12
+COPY --from=build /app/target/ai-teacher-backend /app/ai-teacher-backend
+EXPOSE 8080
+ENTRYPOINT ["/app/ai-teacher-backend"]
diff --git a/backend/pom.xml b/backend/pom.xml
index 1ca8141..c49997f 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -143,12 +143,97 @@
+
+ org.graalvm.buildtools
+ native-maven-plugin
+
+
org.springframework.boot
spring-boot-maven-plugin
+
+
+
+ com.google.cloud.tools
+ jib-maven-plugin
+ 3.4.5
+
+
+
+ gcr.io/distroless/base-nossl-debian12
+
+
+
+ ai-teacher-backend
+
+
+
+ 8080
+
+
+
+
+
+ com.google.cloud.tools.jib.maven.extension.nativeimage.JibNativeImageExtension
+
+
+
+
+
+
+ com.google.cloud.tools
+ jib-native-image-extension-maven
+ 0.1.0
+
+
+
+
+
+ native
+
+
+
+
+
+ org.graalvm.buildtools
+ native-maven-plugin
+ 1.0.0
+
+
+ add-reachability-metadata
+
+ add-reachability-metadata
+
+
+
+ compile
+
+ compile-no-fork
+
+ package
+
+
+
+ ai-teacher-backend
+
+ --initialize-at-build-time=org.slf4j,ch.qos.logback
+ -H:+ReportExceptionStackTraces
+ --gc=serial
+ -Os
+ -H:+RemoveUnusedSymbols
+ -H:-EnableLoggingFeature
+ -R:MaxHeapSize=128m
+ -R:MinHeapSize=32m
+
+
+
+
+
+
+
diff --git a/backend/src/main/java/com/aiteacher/AiTeacherApplication.java b/backend/src/main/java/com/aiteacher/AiTeacherApplication.java
index 679f761..8b8c6c6 100644
--- a/backend/src/main/java/com/aiteacher/AiTeacherApplication.java
+++ b/backend/src/main/java/com/aiteacher/AiTeacherApplication.java
@@ -1,11 +1,15 @@
package com.aiteacher;
+import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
+import com.aiteacher.config.NativeHintsConfig;
+
@SpringBootApplication
@EnableAsync
+@ImportRuntimeHints(NativeHintsConfig.class)
public class AiTeacherApplication {
public static void main(String[] args) {
diff --git a/backend/src/main/java/com/aiteacher/config/NativeHintsConfig.java b/backend/src/main/java/com/aiteacher/config/NativeHintsConfig.java
new file mode 100644
index 0000000..613c2de
--- /dev/null
+++ b/backend/src/main/java/com/aiteacher/config/NativeHintsConfig.java
@@ -0,0 +1,76 @@
+package com.aiteacher.config;
+
+import org.springframework.aot.hint.MemberCategory;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsRegistrar;
+import org.springframework.aot.hint.TypeReference;
+
+/**
+ * GraalVM native-image runtime hints for third-party libraries that use reflection
+ * or classpath resource scanning not covered by Spring Boot's AOT processor.
+ *
+ * Registered via @ImportRuntimeHints on AiTeacherApplication.
+ */
+public class NativeHintsConfig implements RuntimeHintsRegistrar {
+
+ @Override
+ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
+ // PDFBox — font and encoding resources loaded via classpath scanning at runtime
+ hints.resources().registerPattern("org/apache/pdfbox/resources/*");
+ hints.resources().registerPattern("org/apache/pdfbox/resources/afm/*");
+ hints.resources().registerPattern("org/apache/pdfbox/resources/cmap/*");
+ hints.resources().registerPattern("org/apache/pdfbox/resources/glyphlist/*");
+ hints.resources().registerPattern("org/apache/pdfbox/resources/icc/*");
+ hints.resources().registerPattern("org/apache/pdfbox/resources/ttf/*");
+ hints.resources().registerPattern("org/apache/pdfbox/resources/version.properties");
+
+ // PDFBox — font encoding classes instantiated via reflection
+ hints.reflection().registerType(
+ org.apache.pdfbox.pdmodel.font.encoding.GlyphList.class,
+ MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS,
+ MemberCategory.INVOKE_PUBLIC_METHODS
+ );
+ hints.reflection().registerType(
+ org.apache.pdfbox.pdmodel.font.encoding.WinAnsiEncoding.class,
+ MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS
+ );
+ hints.reflection().registerType(
+ org.apache.pdfbox.pdmodel.font.encoding.MacRomanEncoding.class,
+ MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS
+ );
+ hints.reflection().registerType(
+ org.apache.pdfbox.pdmodel.font.encoding.MacExpertEncoding.class,
+ MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS
+ );
+ hints.reflection().registerType(
+ org.apache.pdfbox.pdmodel.font.encoding.StandardEncoding.class,
+ MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS
+ );
+
+ // JPA / Hibernate — array types used in entity mappings
+ hints.reflection().registerType(java.util.UUID[].class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
+
+ // JBoss Logging — message logger implementations generated by annotation processor.
+ // JBoss Logging uses reflection to look up the generated *_$logger class by name.
+ registerJBossLogger(hints, "org.hibernate.jpa.internal.JpaLogger_$logger");
+ registerJBossLogger(hints, "org.hibernate.internal.CoreMessageLogger_$logger");
+ registerJBossLogger(hints, "org.hibernate.internal.EntityManagerMessageLogger_$logger");
+
+ // AWS SDK v2 — HTTP client and SdkPojo serialization
+ hints.resources().registerPattern("software/amazon/awssdk/global/handlers/execution.interceptors");
+ hints.resources().registerPattern("software/amazon/awssdk/services/s3/execution.interceptors");
+ hints.resources().registerPattern("codegen-resources/s3/*");
+ hints.reflection().registerType(
+ software.amazon.awssdk.services.s3.S3Client.class,
+ MemberCategory.INVOKE_PUBLIC_METHODS
+ );
+ }
+
+ private void registerJBossLogger(RuntimeHints hints, String className) {
+ hints.reflection().registerType(
+ TypeReference.of(className),
+ MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS,
+ MemberCategory.INVOKE_PUBLIC_METHODS
+ );
+ }
+}
diff --git a/backend/src/main/resources/application.yaml b/backend/src/main/resources/application.yaml
index 236ed95..b2113a1 100644
--- a/backend/src/main/resources/application.yaml
+++ b/backend/src/main/resources/application.yaml
@@ -27,7 +27,7 @@ spring:
index-type: HNSW
initialize-schema: false
openai:
- api-key: ${OPENAI_API_KEY}
+ api-key: ${OPENAI_API_KEY:}
chat:
options:
model: gpt-4o-mini
@@ -62,8 +62,8 @@ app:
endpoint: https://s3.immich-ad.ovh
region: garage
bucket: ${S3_BUCKET:aiteacher}
- access-key-id: ${S3_ACCESS_KEY_ID}
- secret-access-key: ${S3_SECRET_ACCESS_KEY}
+ access-key-id: ${S3_ACCESS_KEY_ID:}
+ secret-access-key: ${S3_SECRET_ACCESS_KEY:}
min-image-size-px: 100
embedding:
batch-size: 20
diff --git a/docker-compose.native.yml b/docker-compose.native.yml
new file mode 100644
index 0000000..9a60b70
--- /dev/null
+++ b/docker-compose.native.yml
@@ -0,0 +1,37 @@
+version: '3.9'
+
+services:
+ postgres:
+ image: pgvector/pgvector:pg16
+ container_name: aiteacher-postgres-native
+ environment:
+ POSTGRES_DB: aiteacher
+ POSTGRES_USER: aiteacher
+ POSTGRES_PASSWORD: aiteacher
+ ports:
+ - "5432:5432"
+ volumes:
+ - pgdata_native:/var/lib/postgresql/data
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U aiteacher -d aiteacher"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+
+ backend:
+ image: ai-teacher-backend:latest
+ container_name: aiteacher-backend-native
+ env_file:
+ - .env
+ environment:
+ SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/aiteacher
+ SPRING_DATASOURCE_USERNAME: aiteacher
+ SPRING_DATASOURCE_PASSWORD: aiteacher
+ ports:
+ - "8080:8080"
+ depends_on:
+ postgres:
+ condition: service_healthy
+
+volumes:
+ pgdata_native:
diff --git a/specs/005-native-image-deployment/contracts/build-contract.md b/specs/005-native-image-deployment/contracts/build-contract.md
new file mode 100644
index 0000000..5e21faf
--- /dev/null
+++ b/specs/005-native-image-deployment/contracts/build-contract.md
@@ -0,0 +1,47 @@
+# Build Contract: Native Image
+
+**Branch**: `005-native-image-deployment` | **Date**: 2026-04-07
+
+## Overview
+
+This contract defines what the native build produces and how consumers (developers, CI, docker-compose)
+interact with it. The REST API contract is **unchanged** — all existing endpoints remain identical.
+
+## Build Inputs
+
+| Input | Required | Description |
+|-------|----------|-------------|
+| GraalVM JDK 25 | Yes (native profile only) | `JAVA_HOME` must point to GraalVM 25 |
+| `DOCKER_HOST` / Docker daemon | Optional | Required for `jib:dockerBuild`; not needed for `jib:build` |
+| `jib.to.image` Maven property | Optional | Override target image name; defaults to `ai-teacher-backend` |
+
+## Build Outputs
+
+| Profile | Command | Output |
+|---------|---------|--------|
+| Default (JVM) | `mvn package` | `target/ai-teacher-backend-*.jar` |
+| Native | `mvn -Pnative package` | `target/ai-teacher-backend` (native executable) |
+| Native + Docker | `mvn -Pnative package jib:dockerBuild` | Local Docker image `ai-teacher-backend:latest` |
+| Native + Registry | `mvn -Pnative package jib:build` | Remote Docker image (configured via properties) |
+
+## Docker Image Contract
+
+| Property | Value |
+|----------|-------|
+| Base image | `gcr.io/distroless/base-nossl-debian12` |
+| Entrypoint | `/app/ai-teacher-backend` (native executable) |
+| Exposed port | `8080` |
+| Architecture | `linux/amd64` (matches build host) |
+| Required env vars | Same as JVM mode (`SPRING_DATASOURCE_URL`, `OPENAI_API_KEY`, etc.) |
+
+## REST API Contract
+
+Unchanged — the native image exposes the same HTTP API as the JVM image:
+
+- `GET /api/v1/books`
+- `POST /api/v1/books` (multipart PDF upload)
+- `DELETE /api/v1/books/{id}`
+- `POST /api/v1/chat`
+- `GET /api/v1/chat/history`
+
+All endpoints use the same request/response schemas.
diff --git a/specs/005-native-image-deployment/data-model.md b/specs/005-native-image-deployment/data-model.md
new file mode 100644
index 0000000..2da7c34
--- /dev/null
+++ b/specs/005-native-image-deployment/data-model.md
@@ -0,0 +1,33 @@
+# Data Model: Native Image Deployment
+
+**Branch**: `005-native-image-deployment` | **Date**: 2026-04-07
+
+## Summary
+
+This feature introduces **no new data entities**. It is a build and packaging change only.
+
+The existing data model (books, sections, figures, messages, pgvector embeddings) is unchanged.
+All Flyway migrations run identically in native mode as in JVM mode.
+
+## Impact on Existing Entities
+
+| Entity | Change |
+|--------|--------|
+| Books | None |
+| Sections | None |
+| Figures | None |
+| Messages | None |
+| pgvector embeddings | None |
+
+## Configuration Properties (new/changed)
+
+The following new or updated configuration properties are added for native deployment:
+
+| Property | Description | Default |
+|----------|-------------|---------|
+| `jib.to.image` | Target Docker image name/tag | (set at build time) |
+| `jib.to.auth.username` | Docker registry username | (CI env var) |
+| `jib.to.auth.password` | Docker registry password | (CI env var) |
+
+All runtime configuration (database URL, OpenAI API key, S3 credentials) remains in
+`application.properties` / environment variables — unchanged.
diff --git a/specs/005-native-image-deployment/plan.md b/specs/005-native-image-deployment/plan.md
new file mode 100644
index 0000000..2620cff
--- /dev/null
+++ b/specs/005-native-image-deployment/plan.md
@@ -0,0 +1,213 @@
+# Implementation Plan: Native Image Deployment
+
+**Branch**: `005-native-image-deployment` | **Date**: 2026-04-07 | **Spec**: [spec.md](spec.md)
+**Input**: Feature specification from `/specs/005-native-image-deployment/spec.md`
+
+## Summary
+
+Add a `native` Maven profile to the backend that compiles the Spring Boot 4.0.5 application to a
+GraalVM 25 native executable, then packages it into a minimal Docker image via Jib. The JVM build
+(default profile + existing Dockerfile) remains unchanged. A companion `docker-compose.native.yml`
+enables local full-stack testing with the native image. README.md is updated with build
+instructions and an updated architecture diagram.
+
+## Technical Context
+
+**Language/Version**: Java 25 (backend), TypeScript / Node 20 (frontend)
+**Primary Dependencies**: Spring Boot 4.0.5, Spring AI 2.0.0-M4, `native-maven-plugin` 0.10.6,
+ `jib-maven-plugin` 3.4.5, `jib-native-image-extension-maven` 0.1.0
+**Storage**: PostgreSQL 16 + pgvector (unchanged)
+**Testing**: Spring Boot Test / JUnit 5 (unchanged); native integration test via smoke-test on
+ produced Docker image
+**Target Platform**: Linux x86_64 container (Docker), GraalVM 25 CE or Oracle GraalVM 25
+**Project Type**: Web application — backend API + frontend client (Option 2 structure)
+**Performance Goals**: Backend container starts in < 1 s; idle RSS < 150 MB
+**Constraints**: Native profile is opt-in; JVM mode unchanged; no new runtime dependencies;
+ cross-compilation out of scope
+**Scale/Scope**: Single backend deployable unit (1 Docker image); frontend unchanged
+
+## Constitution Check
+
+*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
+
+| Principle | Status | Notes |
+|-----------|--------|-------|
+| I. KISS | ✅ Pass | Build-time change only; no new runtime layers or abstractions. |
+| II. Easy to Change | ✅ Pass | `native` profile is additive; default JVM profile unchanged. Native hints isolated in one `NativeHintsConfig` class. |
+| III. Web-First Architecture | ✅ Pass | REST API contract unchanged. |
+| IV. Documentation as Architecture | ⚠️ Required | README.md MUST be updated with native build instructions and updated Mermaid diagram showing the build pipeline. |
+| Technology Constraints | ✅ Pass | Still 1 backend + 1 frontend deployable unit; no new services. |
+
+**Post-design re-check**: After Phase 1 — Principle IV gate requires README update in same PR.
+
+## Project Structure
+
+### Documentation (this feature)
+
+```text
+specs/005-native-image-deployment/
+├── plan.md # This file
+├── spec.md # Feature specification
+├── research.md # Phase 0 — toolchain & compatibility decisions
+├── data-model.md # Phase 1 — no new entities (confirmed)
+├── quickstart.md # Phase 1 — developer build guide
+├── contracts/
+│ └── build-contract.md # Build inputs/outputs & unchanged REST API
+└── tasks.md # Phase 2 output (/speckit.tasks — NOT created here)
+```
+
+### Source Code (repository root)
+
+```text
+backend/
+├── pom.xml # +native profile, +Jib plugin, +NativeHintsConfig
+├── src/
+│ └── main/
+│ └── java/com/aiteacher/
+│ └── config/
+│ └── NativeHintsConfig.java # NEW — RuntimeHints for PDFBox, AWS SDK gaps
+└── src/
+ └── main/
+ └── resources/
+ └── META-INF/
+ └── native-image/ # Optional: manual JSON hints if AOT gaps remain
+
+docker-compose.yml # UNCHANGED (JVM mode)
+docker-compose.native.yml # NEW — native image + postgres full stack
+README.md # UPDATED — build instructions + Mermaid diagram
+```
+
+**Structure Decision**: Follows Option 2 (web application). Only backend is modified; frontend
+is untouched. New files are minimal: one Java config class, one docker-compose file.
+
+## Complexity Tracking
+
+> No constitution violations. No entry required.
+
+---
+
+## Implementation Phases
+
+### Phase 1: Maven `native` Profile + Native Maven Plugin
+
+**Goal**: `mvn -Pnative package` produces a native executable `target/ai-teacher-backend`.
+
+**Tasks**:
+
+1. Add `` to `backend/pom.xml`:
+ - Wire `spring-boot-maven-plugin` `process-aot` goal to `prepare-package` phase.
+ - Add `native-maven-plugin` 0.10.6 with `add-reachability-metadata` and `compile-no-fork`
+ executions.
+ - Set `ai-teacher-backend` and add essential build args:
+ `-H:+ReportExceptionStackTraces`, `--initialize-at-build-time=org.slf4j`.
+
+2. Create `backend/src/main/java/com/aiteacher/config/NativeHintsConfig.java`:
+ - Implements `RuntimeHintsRegistrar`.
+ - Registers PDFBox classpath resources: `org/apache/pdfbox/resources/**`.
+ - Registers PDFBox reflection entries for font/encoding classes.
+ - Registers AWS SDK `SdkPojo` subtypes used by S3 (discovered iteratively).
+ - Annotate main application class or a `@Configuration` class with
+ `@ImportRuntimeHints(NativeHintsConfig.class)`.
+
+3. Validate: run `mvn -Pnative package -DskipTests` and confirm executable is produced.
+
+**Acceptance**: `target/ai-teacher-backend` exists, `./target/ai-teacher-backend --help`
+exits cleanly, basic startup reaches DB connection stage.
+
+---
+
+### Phase 2: Jib Plugin — Package Native Image into Docker
+
+**Goal**: `mvn -Pnative package jib:dockerBuild` produces a local Docker image.
+
+**Tasks**:
+
+1. Add `jib-maven-plugin` 3.4.5 to the `` section (outside native profile,
+ but configured to package the native executable when the profile is active):
+ - Add `jib-native-image-extension-maven` 0.1.0 as a plugin dependency.
+ - Configure `gcr.io/distroless/base-nossl-debian12`.
+ - Set `ai-teacher-backend` (overridable via `-Djib.to.image`).
+ - Add `` pointing to `JibNativeImageExtension`.
+ - Set `8080`.
+
+2. Verify Jib picks up the native executable from `target/ai-teacher-backend` and sets it
+ as entrypoint.
+
+3. Run `mvn -Pnative package jib:dockerBuild` and confirm:
+ - Image exists in local Docker: `docker images | grep ai-teacher-backend`.
+ - Container starts: `docker run --rm -e SPRING_DATASOURCE_URL=... ai-teacher-backend`.
+
+**Acceptance**: Image starts, logs show "Started AiTeacherApplication in < 1.0 seconds",
+and `GET /api/v1/books` returns 200 (with DB connected).
+
+---
+
+### Phase 3: Integration Smoke Test
+
+**Goal**: All existing features work in native mode.
+
+**Tasks**:
+
+1. Start PostgreSQL locally (or via existing docker-compose.yml).
+2. Start the native container with full environment variables.
+3. Verify:
+ - Flyway migrations run successfully at startup.
+ - `POST /api/v1/books` (PDF upload + embedding) succeeds.
+ - `POST /api/v1/chat` (RAG chat) returns a non-empty answer.
+ - Spring Security HTTP Basic auth is enforced.
+
+4. Fix any `MissingResourceException` or `ClassNotFoundException` by adding entries to
+ `NativeHintsConfig` and rebuilding.
+
+**Acceptance**: All 5 REST API endpoints respond correctly with native image (FR-005).
+
+---
+
+### Phase 4: Docker Compose for Native Stack
+
+**Goal**: `docker compose -f docker-compose.native.yml up` starts the full stack.
+
+**Tasks**:
+
+1. Create `docker-compose.native.yml` at repo root:
+ - `postgres` service: same as `docker-compose.yml` (copy).
+ - `backend` service: uses `ai-teacher-backend:latest` (the Jib-built native image).
+ - Wires all required env vars via `.env` file reference or inline secrets.
+ - Adds `depends_on` with health check for postgres.
+
+2. Document the `.env` file format in `quickstart.md`.
+
+**Acceptance**: `docker compose -f docker-compose.native.yml up` → all services healthy →
+app responds at `http://localhost:8080/api/v1/books`.
+
+---
+
+### Phase 5: README Update (Constitution IV Gate)
+
+**Goal**: README.md reflects the new build pipeline and updated deployment diagram.
+
+**Tasks**:
+
+1. Add "Native Image Build" section to README.md with:
+ - Prerequisite: GraalVM 25 install instructions (sdkman command).
+ - Build command: `mvn -Pnative package jib:dockerBuild`.
+ - Run command: `docker compose -f docker-compose.native.yml up`.
+
+2. Update the Mermaid architecture diagram in README.md to show:
+ - Build pipeline: Maven native profile → GraalVM native-image → Jib → Docker image.
+ - Keep the runtime diagram unchanged (same components, just a lighter container).
+
+**Acceptance**: README.md Mermaid diagram renders correctly on GitHub; build section
+contains all commands from `quickstart.md`.
+
+---
+
+## Risk Log
+
+| Risk | Likelihood | Mitigation |
+|------|------------|------------|
+| Spring AI 2.0.0-M4 missing native hints for some AI response types | Medium | Iterative hint discovery during Phase 3 smoke test |
+| PDFBox font loading fails at runtime in native | Medium | Register full `org/apache/pdfbox/resources/**` in `NativeHintsConfig` |
+| AWS SDK S3 reflection errors | Medium | Switch to `url-connection-client` (simpler); lazy-init S3Client |
+| GraalVM 25 availability in CI | Low | Documented prerequisite; CI pipeline config out of scope (can be added as follow-up) |
+| Native build time > 5 min | Low | Acceptable for a first build; not a blocker for POC |
diff --git a/specs/005-native-image-deployment/quickstart.md b/specs/005-native-image-deployment/quickstart.md
new file mode 100644
index 0000000..02d8b8a
--- /dev/null
+++ b/specs/005-native-image-deployment/quickstart.md
@@ -0,0 +1,81 @@
+# Quickstart: Native Image Build & Deploy
+
+**Branch**: `005-native-image-deployment` | **Date**: 2026-04-07
+
+## Prerequisites
+
+- GraalVM JDK 25 CE or Oracle GraalVM 25 installed and set as `JAVA_HOME`
+- Docker daemon running (for `jib:dockerBuild`)
+- Maven 3.9+
+
+```bash
+# Install GraalVM 25 CE via sdkman
+sdk install java 25-graalce
+sdk use java 25-graalce
+
+# Verify
+java -version # should show GraalVM 25
+native-image --version # should show GraalVM 25
+```
+
+## Build Native Docker Image (local)
+
+```bash
+cd backend
+
+# Build native executable AND package into local Docker image
+mvn -Pnative package jib:dockerBuild
+
+# The image is now available locally
+docker images | grep ai-teacher-backend
+```
+
+## Run the Full Stack (native)
+
+```bash
+# From repo root — starts PostgreSQL + native backend
+docker compose -f docker-compose.native.yml up
+```
+
+Access the app at `http://localhost:8080`.
+
+## Build and Push to Registry (CI)
+
+```bash
+mvn -Pnative package jib:build \
+ -Djib.to.image=ghcr.io/your-org/ai-teacher-backend:native-latest \
+ -Djib.to.auth.username=$REGISTRY_USER \
+ -Djib.to.auth.password=$REGISTRY_TOKEN
+```
+
+## JVM Build (unchanged)
+
+```bash
+# Default profile — no GraalVM required
+cd backend
+mvn package -DskipTests
+
+java -jar target/ai-teacher-backend-*.jar
+```
+
+## Environment Variables (both JVM and native)
+
+| Variable | Description |
+|----------|-------------|
+| `SPRING_DATASOURCE_URL` | PostgreSQL JDBC URL |
+| `SPRING_DATASOURCE_USERNAME` | DB user |
+| `SPRING_DATASOURCE_PASSWORD` | DB password |
+| `OPENAI_API_KEY` | OpenAI API key |
+| `AWS_ACCESS_KEY_ID` | S3 access key (if S3 storage enabled) |
+| `AWS_SECRET_ACCESS_KEY` | S3 secret key |
+| `AWS_REGION` | S3 region |
+
+## Troubleshooting
+
+**Build fails with "ImageGenerationFailed"**: Ensure `native-image` is on `PATH` and
+`JAVA_HOME` points to GraalVM 25, not a regular JDK.
+
+**Missing resource at runtime**: Add the resource pattern to `NativeHintsConfig` and rebuild.
+
+**ClassNotFoundException at runtime**: Register the class in `NativeHintsConfig` with
+`MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS` and rebuild.
diff --git a/specs/005-native-image-deployment/research.md b/specs/005-native-image-deployment/research.md
new file mode 100644
index 0000000..ced9e7f
--- /dev/null
+++ b/specs/005-native-image-deployment/research.md
@@ -0,0 +1,262 @@
+# Research: Native Image Deployment
+
+**Branch**: `005-native-image-deployment` | **Date**: 2026-04-07
+
+## Decision Log
+
+---
+
+### 1. GraalVM Version and Java 25 Support
+
+**Decision**: Use GraalVM JDK 25 (Oracle GraalVM or GraalVM CE 25).
+
+**Rationale**: Oracle GraalVM 25 ships with Java 25 support and includes the
+`native-image` tool. GraalVM CE 25 is available via sdkman (`sdk install java 25-graalce`).
+Both support the `native-maven-plugin` 0.10.x workflow.
+
+**Alternatives considered**:
+- GraalVM 21 with Java 21: would require downgrading `java.version` in pom.xml — rejected
+ because pom.xml already targets Java 25.
+- Mandrel (Red Hat distribution): Red Hat Mandrel 25 may lag behind Oracle GraalVM 25 release;
+ acceptable fallback but Oracle GraalVM 25 is preferred.
+
+---
+
+### 2. Native Image Build Toolchain
+
+**Decision**: Use `native-maven-plugin` (0.10.x) inside a Maven `native` profile, combined
+with Spring Boot's AOT processing via `spring-boot-maven-plugin` `process-aot` execution.
+
+**Rationale**: This is the canonical Spring Boot 4.x native image approach:
+1. `mvn -Pnative package` runs AOT → generates source under `target/spring-aot/`
+2. `native:compile` compiles the AOT-generated sources + app into a native executable.
+
+The `native-maven-plugin` coordinates with GraalVM `native-image` CLI automatically.
+
+**Alternatives considered**:
+- Paketo Buildpacks (`spring-boot:build-image -Pnative`): does not use Jib; rejected because
+ the user explicitly wants Jib.
+- Manual `native-image` CLI: fragile, no Maven lifecycle integration — rejected.
+
+---
+
+### 3. Jib + Native Image Integration
+
+**Decision**: Use `jib-maven-plugin` (3.4.x) with the `jib-native-image-extension-maven`
+extension (0.1.0).
+
+**Rationale**: The Jib native image extension packages a pre-built native executable into a
+Docker image without requiring a Docker daemon. The workflow is:
+
+```
+mvn -Pnative native:compile # step 1: build native executable
+mvn -Pnative jib:dockerBuild # step 2: package into Docker image
+```
+
+Or combined (with `process-aot` wired in `native` profile):
+
+```
+mvn -Pnative package jib:dockerBuild
+```
+
+Jib uses the extension to locate the native executable under `target/` and set it as the
+container entrypoint.
+
+**Key configuration**:
+```xml
+
+ com.google.cloud.tools
+ jib-maven-plugin
+ 3.4.5
+
+
+
+ gcr.io/distroless/base-nossl-debian12
+
+
+
+
+ com.google.cloud.tools.jib.maven.extension.nativeimage.JibNativeImageExtension
+
+
+
+
+
+
+ com.google.cloud.tools
+ jib-native-image-extension-maven
+ 0.1.0
+
+
+
+```
+
+**Alternatives considered**:
+- Custom Dockerfile for native image: more control but manual, diverges from Jib workflow —
+ rejected in favor of Jib as specified.
+- `jib:build` (push to registry) vs `jib:dockerBuild` (local daemon): default to
+ `jib:dockerBuild` for dev; `jib:build` for CI with registry configured.
+
+---
+
+### 4. Docker Base Image for Native Executable
+
+**Decision**: `gcr.io/distroless/base-nossl-debian12` (Debian 12 glibc variant).
+
+**Rationale**: GraalVM native images compiled on Linux link against glibc by default.
+Distroless provides glibc without a shell, minimizing attack surface.
+
+Important: the native executable compiled on the host must target the same glibc ABI as the
+base image. Since the build is on Linux x86_64 this matches Debian 12's glibc 2.36.
+
+**Alternatives considered**:
+- `alpine` (musl libc): requires `--static` or `--libc=musl` native-image flag; more complex —
+ rejected for simplicity (KISS).
+- `gcr.io/distroless/java-base`: includes JVM libs we don't need — rejected (pure native).
+- `scratch`: too minimal; requires fully static binary with all libs included — rejected.
+
+---
+
+### 5. Spring AI 2.0.0-M4 Native Image Compatibility
+
+**Decision**: Rely on Spring AI's built-in AOT hints; add manual `RuntimeHints` only for gaps
+found during build.
+
+**Rationale**: Spring AI 2.0.0-M4 includes `@NativeHint` registrations for the OpenAI
+integration (HTTP client, response DTOs) and pgvector store. Spring Boot's AOT processor
+picks these up automatically. M4 milestone has known native image improvements.
+
+**Known gaps** (require manual RuntimeHints):
+- `spring-ai-pdf-document-reader`: PDFBox font/resource loading uses `Class.forName()` and
+ classpath resource scanning. Need to register font resources and PDFBox parser classes.
+- `spring-ai-advisors-vector-store`: generally AOT-compatible via Spring AI hints.
+
+**Approach**: Add a `NativeHintsConfig` class annotated with `@ImportRuntimeHints` that
+registers PDFBox fonts and any other gaps found during a `-Ob` (quick) native build.
+
+---
+
+### 6. PDFBox Reflection Hints
+
+**Decision**: Register PDFBox font resources and parser classes via a `RuntimeHintsRegistrar`.
+
+**Rationale**: PDFBox 3.x loads fonts from classpath resources (`/org/apache/pdfbox/resources/`)
+and uses reflection for CMap/encoding classes. Without hints the native build fails at runtime
+with `ClassNotFoundException` or missing resource errors.
+
+**Minimum hints needed**:
+```java
+hints.resources().registerPattern("org/apache/pdfbox/resources/**");
+hints.reflection().registerType(org.apache.pdfbox.pdmodel.font.encoding.GlyphList.class,
+ MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
+// Additional classes as discovered during smoke tests
+```
+
+---
+
+### 7. AWS SDK v2 Native Image Support
+
+**Decision**: Add `software.amazon.awssdk:aws-crt-client` is NOT needed; instead add
+`software.amazon.awssdk:netty-nio-client` or use URL connection client.
+Add AWS SDK v2 native image support via `aws-sdk-java-v2-native-bridge` if available,
+otherwise register reflection manually.
+
+**Rationale**: AWS SDK v2 2.30.x provides native image support via:
+- `software.amazon.awssdk:apache-client` or `url-connection-client` (simpler, fewer reflection
+ needs than netty)
+- The SDK's `SdkPojo` interfaces generate reflection entries; Spring Boot AOT may not cover all.
+
+**Minimum approach**: Switch S3 HTTP client from default (netty) to `url-connection-client`
+for simplicity in native mode, register `SdkPojo` subtypes via `RuntimeHints`.
+
+**Alternative**: If S3 is used infrequently and only for figure uploads, mark S3Client as a
+conditional bean initialized lazily — reflection issues then surface only on first use (not
+startup), making native hints easier to discover iteratively.
+
+---
+
+### 8. Flyway in Native Mode
+
+**Decision**: No special configuration needed; Flyway 10.x has native image support.
+
+**Rationale**: Spring Boot 4.x AOT includes Flyway native hints. PostgreSQL JDBC driver
+and Flyway 10 have been tested with GraalVM native. No manual hints required.
+
+---
+
+### 9. Spring Security in Native Mode
+
+**Decision**: No special configuration needed.
+
+**Rationale**: Spring Security 7.x (bundled with Spring Boot 4.x) includes AOT-compatible
+configuration. HTTP Basic auth does not require dynamic proxies at runtime.
+
+---
+
+### 10. Build Command Summary
+
+```bash
+# Install GraalVM 25
+sdk install java 25-graalce
+
+# Build native executable + Docker image (local daemon)
+cd backend
+mvn -Pnative package jib:dockerBuild
+
+# Or push to registry (CI)
+mvn -Pnative package jib:build \
+ -Djib.to.image=ghcr.io/your-org/ai-teacher-backend:native-latest \
+ -Djib.to.auth.username=$CI_USER \
+ -Djib.to.auth.password=$CI_TOKEN
+```
+
+---
+
+### 11. Maven Profile Structure
+
+```xml
+
+
+ native
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ process-aot
+ process-aot
+
+
+
+
+
+ org.graalvm.buildtools
+ native-maven-plugin
+ 0.10.6
+
+
+ add-reachability-metadata
+ add-reachability-metadata
+
+
+ compile
+ compile-no-fork
+ package
+
+
+
+ ai-teacher-backend
+
+ --initialize-at-build-time=org.slf4j
+ -H:+ReportExceptionStackTraces
+
+
+
+
+
+
+
+```
diff --git a/specs/005-native-image-deployment/spec.md b/specs/005-native-image-deployment/spec.md
new file mode 100644
index 0000000..71fefbe
--- /dev/null
+++ b/specs/005-native-image-deployment/spec.md
@@ -0,0 +1,138 @@
+# Feature Specification: Native Image Deployment
+
+**Feature Branch**: `005-native-image-deployment`
+**Created**: 2026-04-07
+**Status**: Draft
+**Input**: Prepare the application for deployment using GraalVM native image for the backend, built into a Docker image via Jib.
+
+## User Scenarios & Testing *(mandatory)*
+
+### User Story 1 - Build Native Docker Image (Priority: P1)
+
+A developer or CI pipeline runs a single Maven command to produce a Docker image containing the
+native-compiled backend binary. The image starts in under one second and uses significantly less
+memory than the JVM fat-jar image.
+
+**Why this priority**: This is the core deliverable — without a working native Docker image the
+entire feature has no value.
+
+**Independent Test**: Run `mvn -Pnative jib:dockerBuild` (or equivalent), start the resulting
+container, hit `GET /api/v1/books`, and receive a valid response. Startup log must show the server
+is ready in under 1 second.
+
+**Acceptance Scenarios**:
+
+1. **Given** a clean checkout with GraalVM installed, **When** the developer runs the native build
+ command, **Then** a Docker image is produced without manual steps.
+2. **Given** the produced Docker image, **When** the container is started with the required
+ environment variables, **Then** the backend starts, connects to PostgreSQL, and serves HTTP
+ requests correctly.
+3. **Given** the produced Docker image, **When** a RAG chat request is made, **Then** it responds
+ correctly (OpenAI call, pgvector retrieval, and Flyway migrations all work in native mode).
+
+---
+
+### User Story 2 - JVM Mode Preserved (Priority: P2)
+
+A developer who has not installed GraalVM can still build and run the backend the same way as
+today (fat-jar / existing Dockerfile), without any change to their workflow.
+
+**Why this priority**: Preserving the developer experience for non-native builds ensures the team
+can keep iterating without a GraalVM prerequisite.
+
+**Independent Test**: Run `mvn package -DskipTests` and start the resulting jar with `java -jar` —
+the application starts and serves requests identically to before this feature.
+
+**Acceptance Scenarios**:
+
+1. **Given** a standard JDK (no GraalVM), **When** `mvn package` is run, **Then** a fat-jar is
+ produced and runs normally.
+2. **Given** the existing Dockerfile, **When** `docker build` is run, **Then** a JVM-mode image
+ is produced and works exactly as before.
+
+---
+
+### User Story 3 - Docker Compose Full Stack (Priority: P3)
+
+A developer can bring up the complete stack (PostgreSQL + native backend) with a single
+`docker compose up` command for local integration testing.
+
+**Why this priority**: Makes it easy to validate the native image against the real database before
+pushing to any environment.
+
+**Independent Test**: Run `docker compose up` (with native image tag), wait for healthy status,
+make a RAG request — all services communicate correctly.
+
+**Acceptance Scenarios**:
+
+1. **Given** the docker-compose.yml, **When** `docker compose up` is run with the native backend
+ image, **Then** all services start, health checks pass, and the application is reachable.
+
+---
+
+### Edge Cases
+
+- What happens when a reflection-heavy library (PDFBox, AWS SDK) is called at runtime without
+ proper native hints? → Must be caught at build time via native-image test or detected early
+ via integration smoke test.
+- What if GraalVM is not installed in CI? → Build must fail with a clear error message, not
+ silently produce a JVM image.
+- What if an environment variable required at runtime is missing? → Container must fail fast with
+ a meaningful error (not a NullPointerException from missing reflection).
+
+## Requirements *(mandatory)*
+
+### Functional Requirements
+
+- **FR-001**: The project MUST provide a `native` Maven profile that compiles the backend to a
+ standalone native executable using GraalVM.
+- **FR-002**: The native profile MUST include Spring Boot AOT processing to generate the
+ reflection, serialization, and proxy hints required at compile time.
+- **FR-003**: The Jib Maven plugin MUST be configured to package the native executable into a
+ Docker image without requiring Docker daemon access during the build.
+- **FR-004**: The resulting Docker image MUST start the backend in under 1 second on modern
+ hardware (2+ CPU cores, 2 GB RAM).
+- **FR-005**: All existing REST endpoints MUST function correctly in native mode (book upload,
+ RAG chat, Flyway migrations, pgvector retrieval, Spring Security).
+- **FR-006**: The JVM fat-jar build (default profile) MUST continue to work unchanged.
+- **FR-007**: The README.md MUST be updated with native build instructions and updated
+ architecture/deployment diagram.
+- **FR-008**: The `docker-compose.yml` MUST be updated (or a companion file added) to support
+ running the native Docker image alongside PostgreSQL.
+
+### Key Entities
+
+- **Native Profile**: Maven build profile (`native`) that activates AOT processing and
+ GraalVM native compilation.
+- **Docker Image**: OCI image produced by Jib, containing the native executable and a minimal
+ base OS layer.
+- **AOT Hints**: Compile-time metadata (reflection, serialization, proxy) that replace runtime
+ reflection for third-party libraries.
+
+## Success Criteria *(mandatory)*
+
+### Measurable Outcomes
+
+- **SC-001**: The native Docker image starts and serves its first HTTP request in under 1 second
+ on a machine with 2+ CPU cores and 2 GB RAM.
+- **SC-002**: The native container's idle memory footprint is at least 40% lower than the
+ equivalent JVM container running the same application.
+- **SC-003**: All existing API endpoints return correct responses in native mode (0 regressions).
+- **SC-004**: A developer with GraalVM installed can build the native Docker image with a single
+ command documented in README.md.
+- **SC-005**: The default JVM build and Dockerfile continue to work without any changes to the
+ developer workflow.
+
+## Assumptions
+
+- GraalVM 25 (CE or Oracle) will be available in CI and on developer machines that want to build
+ native images.
+- The native build is performed on Linux x86_64; cross-compilation for other architectures is out
+ of scope for this feature.
+- Spring Boot 4.0.5 and Spring AI 2.0.0-M4 have sufficient native image support for the features
+ used (OpenAI chat/embedding, pgvector, Flyway, Spring Security, Spring Data JPA).
+- The Docker registry target and image name/tag will be configured via Maven properties or
+ environment variables, not hardcoded.
+- Frontend deployment is out of scope for this feature; only the backend native image is addressed.
+- The existing Dockerfile is kept as a fallback JVM image build; the Jib-built native image is
+ the new primary artifact.
diff --git a/specs/005-native-image-deployment/tasks.md b/specs/005-native-image-deployment/tasks.md
new file mode 100644
index 0000000..3dd2982
--- /dev/null
+++ b/specs/005-native-image-deployment/tasks.md
@@ -0,0 +1,189 @@
+# Tasks: Native Image Deployment
+
+**Input**: Design documents from `/specs/005-native-image-deployment/`
+**Prerequisites**: plan.md ✅, spec.md ✅, research.md ✅, data-model.md ✅, contracts/ ✅
+
+**Tests**: No test tasks — this feature has no unit/integration test requirements in the spec.
+Smoke testing is part of US1 implementation (T010, T011).
+
+**Organization**: Tasks grouped by user story for independent implementation and verification.
+
+## Format: `[ID] [P?] [Story] Description`
+
+- **[P]**: Can run in parallel (different files, no incomplete-task dependencies)
+- **[Story]**: Which user story this task belongs to (US1, US2, US3)
+- Paths use `backend/` and repo root (web app structure per constitution)
+
+---
+
+## Phase 1: Setup (Shared Infrastructure)
+
+**Purpose**: Verify prerequisites and baseline before touching any build files.
+
+- [ ] T001 Verify GraalVM 25 CE or Oracle GraalVM 25 is installed: run `native-image --version` and confirm output shows GraalVM 25; document install command (`sdk install java 25-graalce`) if missing
+- [ ] T002 [P] Verify baseline JVM build passes: run `mvn package -DskipTests` inside `backend/` and confirm `target/ai-teacher-backend-*.jar` is produced
+
+**Checkpoint**: GraalVM available, existing JVM build green — safe to modify build files
+
+---
+
+## Phase 2: Foundational (Blocking Prerequisites)
+
+**Purpose**: Add AOT processing and native compilation support to the Maven build. These changes
+are prerequisites for all user stories.
+
+**⚠️ CRITICAL**: US1 cannot start until T007 confirms the native executable is produced.
+
+- [ ] T003 Add `` skeleton to `backend/pom.xml` and wire `spring-boot-maven-plugin` `process-aot` goal to the `prepare-package` phase inside that profile
+- [ ] T004 Add `native-maven-plugin` 0.10.6 to the `native` profile in `backend/pom.xml` with executions `add-reachability-metadata` and `compile-no-fork` (phase `package`), image name `ai-teacher-backend`, and build args `--initialize-at-build-time=org.slf4j` and `-H:+ReportExceptionStackTraces`
+- [ ] T005 [P] Create `backend/src/main/java/com/aiteacher/config/NativeHintsConfig.java` implementing `RuntimeHintsRegistrar`: register resource pattern `org/apache/pdfbox/resources/**` and reflection for `org.apache.pdfbox.pdmodel.font.encoding.GlyphList` with `MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS`
+- [ ] T006 Add `@ImportRuntimeHints(NativeHintsConfig.class)` annotation to the main `@SpringBootApplication` class in `backend/src/main/java/com/aiteacher/`
+- [ ] T007 Run `mvn -Pnative package -DskipTests` inside `backend/` and confirm `target/ai-teacher-backend` native executable is produced; fix any AOT compilation errors before proceeding
+
+**Checkpoint**: `target/ai-teacher-backend` exists and exits cleanly — US1 implementation can begin
+
+---
+
+## Phase 3: User Story 1 — Build Native Docker Image (Priority: P1) 🎯 MVP
+
+**Goal**: One command (`mvn -Pnative package jib:dockerBuild`) produces a local Docker image
+containing the native backend. Container starts in < 1 s and all REST endpoints work.
+
+**Independent Test**: Start native container with DB env vars → `GET /api/v1/books` returns 200 →
+startup log shows ready in under 1 second.
+
+### Implementation for User Story 1
+
+- [ ] T008 [US1] Add `jib-maven-plugin` 3.4.5 to `backend/pom.xml` `` section (default build, not inside `native` profile): configure `gcr.io/distroless/base-nossl-debian12`, `ai-teacher-backend`, exposed port `8080`, and `` referencing `com.google.cloud.tools.jib.maven.extension.nativeimage.JibNativeImageExtension`; add `jib-native-image-extension-maven` 0.1.0 as plugin ``
+- [ ] T009 [US1] Run `mvn -Pnative package jib:dockerBuild -DskipTests` inside `backend/` and confirm `docker images | grep ai-teacher-backend` shows the produced image
+- [ ] T010 [US1] Start the native container with required env vars (`SPRING_DATASOURCE_URL`, `SPRING_DATASOURCE_USERNAME`, `SPRING_DATASOURCE_PASSWORD`, `OPENAI_API_KEY`) pointing to a local PostgreSQL; verify startup log shows "Started … in < 1.0 seconds" and `GET /api/v1/books` returns HTTP 200
+- [ ] T011 [US1] Run full smoke test against the running native container: `POST /api/v1/books` (PDF upload), `POST /api/v1/chat` (RAG query), `GET /api/v1/chat/history`, and HTTP Basic auth enforcement; for each `MissingResourceException` or `ClassNotFoundException` found, add the corresponding entry to `backend/src/main/java/com/aiteacher/config/NativeHintsConfig.java` and rebuild (T009 → T010 loop) until all pass
+
+**Checkpoint**: Native Docker image starts < 1 s, all 5 endpoints work — US1 complete ✅
+
+---
+
+## Phase 4: User Story 2 — JVM Mode Preserved (Priority: P2)
+
+**Goal**: Default Maven build and existing Dockerfile continue to work with no changes required
+from developers who do not have GraalVM installed.
+
+**Independent Test**: `mvn package -DskipTests` produces a fat-jar; `docker build` using
+`backend/Dockerfile` produces a working JVM container.
+
+### Implementation for User Story 2
+
+- [ ] T012 [P] [US2] Verify `mvn package -DskipTests` (no `-Pnative`) inside `backend/` still produces `target/ai-teacher-backend-*.jar`; confirm the jar starts correctly with `java -jar`
+- [ ] T013 [P] [US2] Verify `docker build -t ai-teacher-backend-jvm backend/` using the existing `backend/Dockerfile` succeeds and the resulting JVM container starts and serves `GET /api/v1/books` correctly
+
+**Checkpoint**: JVM path fully unchanged — US2 complete ✅
+
+---
+
+## Phase 5: User Story 3 — Docker Compose Full Stack (Priority: P3)
+
+**Goal**: `docker compose -f docker-compose.native.yml up` starts PostgreSQL + native backend
+together so developers can run the complete native stack locally with a single command.
+
+**Independent Test**: `docker compose -f docker-compose.native.yml up` → all services healthy →
+`GET http://localhost:8080/api/v1/books` returns 200.
+
+### Implementation for User Story 3
+
+- [ ] T014 [US3] Create `docker-compose.native.yml` at repo root: include `postgres` service (same image and config as `docker-compose.yml`), add `backend` service using image `ai-teacher-backend:latest` with all required env vars sourced from `.env`, `depends_on` postgres with health-check condition, and port mapping `8080:8080`
+- [ ] T015 [P] [US3] Create `.env.example` at repo root listing all env vars needed by the native stack (`SPRING_DATASOURCE_URL`, `SPRING_DATASOURCE_USERNAME`, `SPRING_DATASOURCE_PASSWORD`, `OPENAI_API_KEY`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`) with placeholder values and inline comments
+
+**Checkpoint**: Full native stack starts via docker compose — US3 complete ✅
+
+---
+
+## Phase 6: Polish & Cross-Cutting Concerns
+
+**Purpose**: Constitution IV gate (README must be updated in the same PR as architectural change)
+and final documentation alignment.
+
+- [ ] T016 Update `README.md` at repo root: add a "Native Image Build" section with GraalVM install command (`sdk install java 25-graalce`), build command (`mvn -Pnative package jib:dockerBuild`), and run command (`docker compose -f docker-compose.native.yml up`); update the Mermaid architecture diagram to show the native build pipeline (Maven native profile → GraalVM native-image → Jib → Docker image)
+- [ ] T017 [P] Update `specs/005-native-image-deployment/quickstart.md` with any corrections or additions discovered during implementation (env var names, exact commands, troubleshooting entries)
+
+---
+
+## Dependencies & Execution Order
+
+### Phase Dependencies
+
+- **Setup (Phase 1)**: No dependencies — start immediately
+- **Foundational (Phase 2)**: Requires Phase 1 completion — **BLOCKS all user stories**
+- **US1 (Phase 3)**: Requires Phase 2 (T007 must pass) — primary deliverable
+- **US2 (Phase 4)**: Requires Phase 2 (T003–T006 must not break JVM path) — can run in parallel with US1 after Phase 2
+- **US3 (Phase 5)**: Requires US1 completion (T009 must produce the Docker image)
+- **Polish (Phase 6)**: Requires US1, US2, US3 all complete
+
+### User Story Dependencies
+
+- **US1 (P1)**: Unblocked after Foundational — critical path
+- **US2 (P2)**: Unblocked after Foundational — can run in parallel with US1 (different verification commands, no file conflicts)
+- **US3 (P3)**: Depends on US1 (needs the Docker image to exist for docker-compose)
+
+### Within US1
+
+T008 (Jib config) → T009 (build image) → T010 (verify startup) → T011 (smoke test + hint fixes, may loop back to T009)
+
+### Parallel Opportunities
+
+- T001 and T002 (Phase 1): both can run in parallel
+- T003/T004 (pom.xml edits) are sequential (same file); T005 (new Java file) can run in parallel with T003/T004
+- T012 and T013 (US2 verification): parallel, different commands
+- T014 and T015 (US3 docker-compose + .env.example): parallel, different files
+- T016 and T017 (Polish): parallel, different files
+- US1 (Phase 3) and US2 (Phase 4) can run in parallel after Foundational completes
+
+---
+
+## Parallel Example: Foundational Phase
+
+```bash
+# In parallel — different files, no dependencies between them:
+Task T005: "Create NativeHintsConfig.java in backend/src/main/java/com/aiteacher/config/"
+
+# Sequential — same file (pom.xml):
+Task T003: "Add native profile skeleton with spring-boot-maven-plugin process-aot"
+Task T004: "Add native-maven-plugin 0.10.6 to native profile" # after T003
+```
+
+## Parallel Example: After Foundational Completes
+
+```bash
+# US1 and US2 can start simultaneously:
+Developer A → Phase 3 (US1): T008 → T009 → T010 → T011
+Developer B → Phase 4 (US2): T012, T013 (parallel)
+```
+
+---
+
+## Implementation Strategy
+
+### MVP First (User Story 1 Only)
+
+1. Complete Phase 1: Setup (T001, T002)
+2. Complete Phase 2: Foundational (T003–T007) — **CRITICAL, do not skip T007**
+3. Complete Phase 3: US1 (T008–T011)
+4. **STOP and VALIDATE**: Native image starts < 1 s, all endpoints work
+5. This alone satisfies FR-001 through FR-006 and SC-001 through SC-003
+
+### Incremental Delivery
+
+1. Phase 1 + Phase 2 → build toolchain ready
+2. Phase 3 (US1) → native Docker image working (**MVP**)
+3. Phase 4 (US2) → JVM fallback confirmed
+4. Phase 5 (US3) → full native stack via docker compose
+5. Phase 6 (Polish) → README updated (constitution IV gate — **required before merge**)
+
+---
+
+## Notes
+
+- [P] tasks operate on different files or independent commands — safe to run concurrently
+- US2 (T012, T013) can be verified at any time after Phase 2; they are quick sanity checks
+- The hint-fix loop in T011 is the most likely source of iteration — budget extra time
+- Constitution IV gate (README update, T016) is **mandatory** — PR cannot merge without it
+- `.env.example` (T015) should be committed; actual `.env` must be in `.gitignore`