Skip to content

MIP-0008 tasks

Ordered delivery of MIP-0008 as stacked PRs (.claude/skills/mip-tasks/SKILL.md, scripts/stack.sh). Branch mip-0008/<k>-<slug>, each based on the previous; merge bottom-up, scripts/stack.sh restack after each squash-merge.

# slug delivers tests (must exist before the PR) depends on
1 maps-url Coordinates.fromMapsUrl (core, pure: /@lat,lon, q=/query=/ll=, !3dlat!4dlon), --location-url in Main (after --lat/--lon, before the env vars; an unreadable URL warns and falls through), docs (ARCHITECTURE.md §3.1, RUN-LOCALLY.md §4), this task list, MIP → Accepted CoordinatesSpec (core): the four URL shapes, a URL-encoded comma, the pin winning over the viewport, garbage/non-Google/out-of-range → None, a short link → None; live: just run -- --brief --location-url <place url> prints source: --location-url –
2 docker-jvm Dockerfile (builder: sbt cli/assembly; jvm: Temurin 25 JRE alpine + the jar; dev: nixos/nix with the flake's shell warmed), .dockerignore, docker-compose.yml (marola on :jvm, ollama + one-shot ollama-pull, profiles ollama and local, .env mounted), just docker-build/docker-run/docker-compose, hadolint in just quality (skipped when not installed, like ruff), RUN-LOCALLY.md §11 "Docker only", README quick-start row, ARCHITECTURE.md §11 Phase 3 hadolint Dockerfile (in just quality); docker build --target jvm then docker run … --brief --lat -27.6733 --lon -48.47 live and docker compose --profile ollama run marola --summarize … (this needs a Docker daemon, which the agent's session does not have: run on the host before merging, or let task 4's workflow be the first build) 1
3 docker-native sbt-native-image 0.5.0 on cli (nativeImageInstalled, --initialize-at-build-time=org.slf4j,ch.qos.logback,com.fasterxml.jackson, -march=compatibility), reachability metadata under cli/src/main/resources/META-INF/native-image/ (the root-level *.json resources: the two compiled prompts, sea_lore.json, benchmark_questions.json; sun.misc.Signal reflection for Kyo's signal handler), just native-image (GraalVM from nix shell nixpkgs#graalvmPackages.graalvm-ce, not added to the flake, 700 MB), Dockerfile stages native-build (ghcr.io/graalvm/native-image-community:25) and native (gcr.io/distroless/base-debian12:nonroot), docs sbt cli/nativeImage locally; the binary runs --brief and --summarize (Ollama, llama3.2:1b) live with the same output as the JVM; just test unchanged (the golden suite stays on the JVM, MIP §7) 2
4 docker-ci .github/workflows/docker.yml: on PR → hadolint + build :jvm (no push, GHCR cache); on main → build and push :jvm (amd64+arm64, jar built once on the build platform), :native (amd64), :dev to ghcr.io/h0ffmann/marola, each also tagged <target>-<sha>; docker/metadata-action + docker/build-push-action with GITHUB_TOKEN (packages: write); docs: RUN-LOCALLY.md §11 uses the published tags actionlint (just quality); the PR's own run builds :jvm; first push run after merge 3
5 smoke-workflow .github/workflows/docker-smoke.yml (workflow_dispatch + daily 09:30 UTC): inputs lat, lon, maps_url, model (llama3.2:1b), image (ghcr.io/h0ffmann/marola:jvm); short link → curl -sIL → --location-url, else --lat/--lon, else Campeche; Ollama binary + model cached as in marola-e2e.yml; docker run --network host $image --summarize …; scripts/smoke_record.py turns the stdout into smoke/<run-id>.json + smoke/latest.json + smoke/history.json (50 runs) committed to the orphan site-data branch (contents: write), artifact uploaded, site.yml triggered (actions: write) python3 scripts/smoke_record.py --self-test (a recorded --summarize stdout → top pick, draft, reviewer score/verdict, exit code; a failed run → ok: false and no summary) in just quality; actionlint; one manual run after merge 4
6 smoke-panel site.yml: check out site-data (if it exists) into site/dist/smoke/; app.js: fetch smoke/latest.json if present → "Last live run" panel in the footer (when, where, model, image, top pick, the summary marked as LLM text, the reviewer's score/verdict, a link to the run; verdict reject → numbers only) + a marker at the run's origin; smoke/history.json → the last 10 runs; style.css; MIP §8 "public model text" wording in the panel manual (just site-build floripa, drop a sample smoke/latest.json into site/dist/smoke/, just site-serve, see the panel and the marker); actionlint; SiteBuilderSpec unchanged 5
7 marola-local Dockerfile.local (ollama/ollama + a model-store layer with marola-llama3.2 created from finetune/Modelfile at build time, Tier 1), compose profile local pointing at it, docker.yml job local (on main and workflow_dispatch): build, push :local and :local-<sha>, then the benchmark gate: just benchmark against the image's Ollama and scripts/benchmark_gate.py comparing the summary table with docs/benchmarks/: fails when rag-general coverage (all) drops below the kept run's or below baseline's; finetune/README.md "as an image" section; MIP → Implemented after merge python3 scripts/benchmark_gate.py --self-test (parses the kept docs/benchmarks/2026-09-05.md, passes on it, fails on a doctored copy) in just quality; actionlint; one workflow_dispatch run after merge 6

Decisions taken for v1 (MIP §11), so that the tasks above are unambiguous:

  1. Native-image: yes. The spike (2026-09-05, GraalVM CE 25.2.4 = JDK 25.0.4, sbt-native-image 0.5.0, Kyo 1.0.0-RC5, Scala 3.9.0): the image builds in 44 s once slf4j/logback/Jackson are initialised at build time (the first two failures were exactly those, reached through a static logger and the MCP SDK's Jackson); the 69 MB binary runs the live --brief path in 35 s wall / 75 MB RSS (all of it Overpass/Open-Meteo). --summarize needs the compiled prompts as resources: metadata, task 3. Kyo itself needed nothing. The MCP server stays JVM (MIP §4).
  2. :jvm base: eclipse-temurin:25-jre-alpine (71 MB, 2026-08-21; 25-jre is 111 MB, both checked on Docker Hub 2026-09-05). No jlink for v1: the 40 MB it might save is not worth a second build path. Builder: sbtscala/scala-sbt:eclipse-temurin-25.0.4_7_1.13.0_3.8.4 (466 MB, 2026-09-05). Native builder ghcr.io/graalvm/native-image-community:25, runtime gcr.io/distroless/base-debian12:nonroot (both manifests answered 200 on 2026-09-05).
  3. Smoke test cadence: daily at 09:30 UTC (06:30 in Brazil, before the morning swim) plus on demand. Never per PR (MIP §8: ~5 min of runner per run).
  4. Where the smoke result lives: the orphan site-data branch, copied into site/dist/smoke/ by site.yml; the smoke workflow never deploys Pages itself (MIP §5.5).
  5. Tier 2 adapter: not in v1. marola-local is the Tier 1 Modelfile model on llama3.2 (finetune/README.md says so already); an adapter shipped as a release asset is a later task, once something has trained it.
  6. The panel is on the public map, in the footer, labelled as a live run of the model with the reviewer's verdict next to it; a reject verdict hides the sentence and keeps the numbers. A separate /status.html is not worth a second page for one panel.
  7. Platforms: :jvm and :dev for linux/amd64 and linux/arm64 (the jar is built once, on the build platform, and copied into each); :native and :local for linux/amd64 only (native-image does not cross-compile; the Ollama model store is platform-independent but the base image build is done once).
  8. Ollama in compose is the official ollama/ollama:latest (3.5 GB, 2026-09-03), pulled once into a named volume; CI uses the ~100 MB binary as marola-e2e.yml does.
  9. Benchmark gate metric: rag-general coverage (all) from the summary table, compared with the best table of the newest file in docs/benchmarks/ (that file keeps several runs; the best is run 2's 0.84): a drop of more than 0.05, or falling below the new run's own baseline, fails the job and :local is not moved. One number, the one docs/benchmarks/2026-09-05.md calls "the result that matters". Verified on the last local run (data/benchmark-20260905-0950.md: 0.83 vs 0.84, baseline 0.73 → pass).

Restack notes: task 3 appends stages to task 2's Dockerfile and adds keys to build.sbt (task 1 does not touch it); task 7 replaces the local profile's services in task 2's compose file (its own workflow, docker-local.yml, leaves task 4's untouched). Nothing else rewrites a file an earlier task created; tasks 5 and 6 touch only files that are already on main (MIP-0005's page and workflow) or new ones.