fix(docker): bump rust 1.85 -> 1.90 + enforce LF on shell scripts

Two real bugs found while pushing the v0.8.0 image to Docker Hub:

## Rust 1.85 -> 1.90

`hnsw_rs 0.3.4` (transitive via wifi-densepose-ruvector ->
ruvector-attn-mincut -> hnsw_rs) calls `nbp.is_multiple_of(500_000)`.
`is_multiple_of` on unsigned integers was stabilised in Rust 1.87
(rust-lang/rust#128101 — RFC 3565). On 1.85 the compile fails with:

  error[E0658]: use of unstable library feature `unsigned_is_multiple_of`
   --> hnsw_rs-0.3.4/src/hnswio.rs:736:20

Pinned to 1.90 for reproducibility — a comment in the Dockerfile flags
the 1.87 MSRV requirement so a future downgrade can't quietly break it.

## .gitattributes — force LF on shell scripts + Dockerfile

Without a `.gitattributes`, git's default `core.autocrlf=true` on
Windows converts shell scripts to CRLF on checkout. `COPY`ing
`docker/docker-entrypoint.sh` into a Linux image then preserves CRLF.
The shebang line `#!/bin/sh\r\n` causes `exec /app/docker-entrypoint.sh`
to fail with:

  exec /app/docker-entrypoint.sh: no such file or directory

The kernel tries to look up an interpreter literally named `/bin/sh\r`,
which doesn't exist. Container exits immediately. The first v0.8.0
image push (digest sha256:7957…44fa) suffered exactly this; the
re-pushed image (digest sha256:e9f4…d38315) was built on a
renormalised tree.

The .gitattributes rule forces LF for:
  - *.sh / *.bash
  - Dockerfile*
  - docker/* (covers docker-entrypoint.sh + docker-compose.yml)
  - scripts/*
  - `verify` (the proof-replay wrapper — same root cause as if it
    had landed CRLF in someone's clone)

Binary file globs (*.bin, *.wasm, *.rvf, *.pcap, etc.) explicitly
marked binary so text-normalisation never touches them.

## CHANGELOG — drop the false `--introspection` flag claim

The CHANGELOG entry for v0.8.0 said the introspection endpoints were
"off by default, enabled via `--introspection`". That isn't true:
`sensing-server --help` has no such flag. The routes are mounted
unconditionally in `main.rs`. The per-frame `update()` p99 of
0.041 ms (~24× under D4's 1 ms budget) makes always-on viable; the
"off by default" framing came from an earlier draft of ADR-099 that
the implementation outgrew. Corrected.

## Verification

End-to-end smoke test of the pushed image:

  docker run -d -p 13000:3000 -e CSI_SOURCE=simulated     -e SENSING_BIND_ADDR=0.0.0.0 ruvnet/wifi-densepose:v0.8.0

  /health -> {"status":"ok","source":"simulated",...}
  /api/v1/info -> {"backend":"rust","features":{"ruvector":true,"signal_processing":true,...}}
  /api/v1/introspection/snapshot -> {"regime":"unknown",
    "regime_changed":false,"top_k_similarity":[]} (ADR-099 shape exact)
  /ui/observatory.html -> HTTP 200, 15 KB

Published manifest digests:
  ruvnet/wifi-densepose:v0.8.0 -> sha256:e9f4c5af…d38315
  ruvnet/wifi-densepose:latest -> sha256:e9f4c5af…d38315

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
ruv 2026-05-14 13:38:58 -04:00
parent f396c44751
commit 84638314a4
3 changed files with 42 additions and 2 deletions

35
.gitattributes vendored Normal file
View File

@ -0,0 +1,35 @@
# Line-ending policy.
#
# `* text=auto` lets git normalise text files to LF in the repository and convert
# to the platform's native line endings on checkout. That default is fine for
# .md / .rs / .toml / .py — broken for shell scripts and Dockerfiles, where
# CRLF on the shebang line causes Linux exec to look for an interpreter named
# `/bin/sh\r` (or similar) and fail with "no such file or directory".
#
# Force LF for anything that ends up executed inside a Linux container or a
# POSIX shell. This is what prevented the v0.8.0 image from booting at first
# build until the entrypoint was renormalised.
* text=auto
*.sh text eol=lf
*.bash text eol=lf
verify text eol=lf
Dockerfile* text eol=lf
docker/* text eol=lf
scripts/* text eol=lf
# Binary blobs that should never be touched by text-normalisation.
*.bin binary
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.zip binary
*.tar binary
*.tgz binary
*.gz binary
*.wasm binary
*.rvf binary
*.task binary
*.csi.jsonl binary
*.pcap binary

View File

@ -14,7 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
regime classification) and `temporal-compare` (DTW pattern matching) as a
**parallel tap** alongside RuView's existing event pipeline — no replacement,
no behaviour change to the existing `/ws/sensing` fan-out or `wifi-densepose-signal`
DSP. Two new endpoints (off by default, enabled via `--introspection`):
DSP. Two new endpoints (always mounted — the tap is cheap enough at 0.041 ms p99
per-frame `update()` to ship hot by default):
- `GET /ws/introspection` — newline-delimited JSON snapshots streamed at the CSI
frame rate. Each snapshot carries `frame_count`, `regime` (Idle / Periodic /
Transient / Chaotic / Unknown), `lyapunov_exponent`, `attractor_dim`,

View File

@ -3,7 +3,11 @@
# Multi-stage build for minimal final image
# Stage 1: Build
FROM rust:1.85-bookworm AS builder
# Rust 1.87+ is required: `hnsw_rs 0.3.4` (transitive via wifi-densepose-ruvector ->
# ruvector-attn-mincut) uses `u*::is_multiple_of`, stabilised in 1.87. Pinning to a
# recent stable (1.90) for reproducibility — bump cautiously since reproducible
# builds rely on this.
FROM rust:1.90-bookworm AS builder
WORKDIR /build