Conversation
This reverts commit 840c88d.
`make test` and `make audit` (and thus root `make test`/`make audit`) could report green even when Vitest tests failed or pnpm audit found vulnerabilities, because both targets masked their exit code (`|| true`, `2>/dev/null || echo ...`). Found while wiring up the autopilot quality gate, which depends on these commands being honest.
The autopilot ci-build-optimization pipeline uses develop as its integration branch (base) in pr_ci mode. Without develop in pull_request.branches, phase PRs into develop would trigger zero CI checks, and autopilot's merge guard treats that as SKIPPED rather than green — it would refuse to ever merge a phase.
Adds the autopilot stack profile, the 7-phase pipeline plan derived from OPTIMIZATION_SPEC.md (rust-optimizer's CI/build/Docker/dependency audit), and the seeded run ledger. autonomy: pr_ci, base: develop (created on orchestrate's first firing), trunk: main — phase PRs land on develop; a single develop -> main PR is the human review point for the full body of work.
…> Just migration) User request: convert the project's Make-based task runner to just, scoped as a phase to run after everything else. Investigated first: just 1.57.0 is already installed locally; no CI workflow, Dockerfile, or husky hook invokes make directly today — only .autopilot/profile.yml's commands.* and human-facing docs do. That's what makes phase 7 need to be strictly last (depends_on: [0..6]): it rewrites profile.yml's make commands to just commands, which every earlier phase's gate is still relying on while those phases run.
… phase 4 baseline
…ges fixed
The cross-vendor integration court (GPT jury, Claude writer — the first
properly-seated panel in this pipeline) returned REMAND with two charges.
Both were verified against the repo and both were real; both had been
missed by the author's own Tier-3 reviews.
1. FALSE-GREEN RELEASE ASSURANCE. Phase 6's docker.yml pull_request filter
omitted inputs the image actually COPYs — backend/entrypoint.sh,
backend/migrations/**, backend/rust-toolchain.toml, and the ruvector
submodule gitlink. A packaging regression in any of them could merge with
the Docker jobs never running, defeating the guarantee the trigger exists
to provide. Filter now covers every COPYed input; backend/src/** stays
excluded deliberately, with the reason recorded inline.
2. INCOMPLETE TASK-RUNNER MIGRATION. Live `make` commands survived phase 7
in user-facing places the sweep never covered (it grepped docs and
`make <target>` in scripts, but not source, and not the `make -C` form):
- OnboardingFlow.tsx:122 — the PRODUCT UI showed users `make dev`
- backend/src/main.rs:207 — the CLI printed `Run 'make models'`
- scripts/setup-ai.sh, scripts/setup-validate.sh — `make -C ...`
All fixed. A repo-wide sweep across .sh/.rs/.tsx/.ts/.yml/.json now finds
no live make invocation. cargo check, tsc --noEmit, shellcheck, just lint
and just format-check all clean afterwards.
A third charge (ADR-032 marked Accepted) was NOT upheld: this repo uses
Accepted for implemented decisions and Proposed for design-only (cf ADR-031).
Court record: .autopilot/court/ci-build-optimization/integration.md
…ges re-verified resolved
Owner
Author
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
1. --healthcheck was never implemented (pl-healthcheck-flag-unhandled)
docker-compose.yml's backend healthcheck runs `/app/emailibrium
--healthcheck`, but main.rs parsed no such flag, so it fell through to
normal startup: the probe launched a SECOND server every 30s and the
container could never report healthy. It has to be the binary rather than
an HTTP probe — the runtime image ships neither wget nor curl (verified).
Implemented as a short-circuit that probes the running server over
loopback at /api/v1/vectors/health and exits 0/1. PROVEN by diffing old
vs new images:
pre-fix : flag ignored -> server startup path -> "unable to open
database file"
with fix: "healthcheck: http://127.0.0.1:8080/... unreachable", exit 1
The old binary was opening the database; the new one only makes an HTTP
call. The exits-0-when-healthy path needs a live server — see item 3.
2. Lighthouse could not fail (pl-lighthouse-warn-only)
All 8 assertions were severity "warn", and lhci exits 0 when only warns
trip — so phase 2's "real pass/fail instead of theater" was cosmetic.
Measured the actual scores before touching thresholds: performance 0.98,
accessibility 0.95, best-practices 1.00, seo 0.92, all four metric budgets
inside limits. Everything passes with headroom, so flipping to "error" was
free. Re-ran after the flip: lhci exits 0 as a HARD gate.
3. Nothing proved the system RUNS (pl-frontend-image-needs-compose-to-run)
Added .github/workflows/smoke.yml: builds the images, `docker compose up
--wait` (which fails unless every healthcheck passes), curls the backend
health endpoint, and asserts the frontend actually returns HTML — the only
place that can be checked, since its nginx proxies to a compose-only
upstream. This is the container-start/health smoke test the integration
court named as the single highest-value missing thing.
4. Debuginfo tradeoff (pl-test-debuginfo-tradeoff)
CARGO_PROFILE_TEST_DEBUG was 0, stripping every `at file:line` frame from
RUST_BACKTRACE output and defeating RUST_BACKTRACE: 1 two lines above it.
Set to 1 (line-tables-only): keeps file:line at a fraction of full
debuginfo, so target/ stays lean. MEASURED with a throwaway panicking
test, since a Mach-O section check is meaningless on macOS (DWARF stays in
.o files): debug=0 -> 1 frame with file:line; debug=1 -> 7.
Also recorded, NOT fixed: pl-postgres-volume-permissions. `docker compose up
--wait` fails locally on OrbStack because postgres (user 999, read_only,
cap_drop ALL) cannot chmod the root-owned named volume — the same class as
the /app/data bug Tier-3 caught in phase 5. Whether it is real on Linux is
UNRESOLVED; the new smoke job runs on ubuntu-latest and is the authority.
That job may therefore fail on first run. If it does, it has found a genuine
bug rather than a flaw in the test.
… start The compose stack has never come up. postgres died during initdb with "could not change permissions of directory /var/lib/postgresql/data", reproduced identically on ubuntu-latest, so this was never a local quirk. Root cause is a UID mismatch: postgres:16-alpine runs postgres as uid 70 and owns /var/lib/postgresql as 70:70, but the service was pinned to 999:999 — the DEBIAN postgres uid. The config was presumably written for postgres:16 and later switched to -alpine. cap_add could not compensate: Docker's added capabilities are inert for a non-root user, because Linux clears effective capabilities on the transition and Docker sets no ambient capabilities. So the FOWNER in cap_add never applied, and the container owned nothing it needed to write. Fix is the uid alone. Every hardening property is retained: non-root, cap_drop: ALL, read_only, no-new-privileges, tmpfs mounts. redis is left at 999 deliberately — redis:7-alpine really does use that uid, and it reported healthy in the smoke run. Found by .github/workflows/smoke.yml on its first execution, which is the job's entire purpose: docker.yml proves images BUILD, smoke proves the system STARTS. Nothing had ever tested the latter.
The backend exited 1 half a second after start in the compose smoke test: ERROR ONNX embedding model initialization failed: Failed to retrieve model.onnx. No embedding provider available. Error: ... No fallback provider available in production. Not a timeout (it died in 0.5s, nowhere near the 300s wait), not the postgres uid (that fix worked — postgres now starts), and not the base image (the backend already runs on debian:trixie-slim). Root cause: fastembed's default cache is `.fastembed_cache` RELATIVE TO CWD, so /app/.fastembed_cache — which sits on the read-only rootfs, because docker-compose.yml sets read_only: true and mounts only /tmp (tmpfs) and /app/data (volume) as writable. embedding.onnx.cache_dir was unset in every config, so OnnxEmbeddingModel::new never called with_cache_dir() and fastembed fell back to that unwritable default. The download failed, and the "onnx" provider has no fallback in a production build, so startup aborted. Point cache_dir at /app/data/models — the writable named volume, whose uid 1000 ownership was fixed in phase 5 (verified again here). The model now also persists across restarts rather than re-downloading. Consequence worth knowing: first start now pulls ~90MB from HuggingFace, so the smoke test acquires a network dependency and a slower first boot. The healthcheck's start_period (45s) plus --wait-timeout 300 should cover it; CI will confirm. Third layer uncovered by .github/workflows/smoke.yml — after postgres permissions and before it, nothing had ever started this stack.
… starts Fourth and (so far) final layer uncovered by the compose smoke test. Two independent frontend defects, both fatal at startup: 1. `mkdir() "/var/cache/nginx/client_temp" failed (13: Permission denied)` The tmpfs mounts were declared without uid/gid. A tmpfs defaults to root-owned mode 755 and this container runs as 1000, so nginx could not create its cache dirs. Note the backend's /tmp already carried mode=1777 while none of the frontend's three mounts had anything — the difference was never noticed because nothing started the stack. 2. `[emerg] host not found in upstream "backend"` nginx resolves proxy_pass targets at CONFIG LOAD, not per request, and the frontend had NO depends_on at all — so it started in parallel with the backend and lost the race. They do share the frontend-proxy network, so this was purely ordering, not isolation; now gated on service_healthy. These are the same defect SHAPE as the previous three: the compose hardening (read_only + non-root user + cap_drop ALL) was applied wholesale without giving each service a writable path it actually owns — postgres: root-owned named volume, user uid mismatched the image (999 vs 70) backend : fastembed cache resolved onto the read-only rootfs frontend: root-owned tmpfs mounts Every one of them was fatal, and none was observable until .github/workflows/ smoke.yml actually started the stack.
Fifth layer from the compose smoke test. nginx started cleanly and served traffic — workers up, no emerg — but the container reported unhealthy forever, so `docker compose up --wait` failed. nginx.conf declares `listen 80;` only, i.e. IPv4. The nginx image ships 10-listen-on-ipv6-by-default.sh to add the `listen [::]:80;` line, but it cannot: read_only: true makes /etc/nginx/conf.d/default.conf unwritable, and the script logs "can not modify ... (read-only file system?)" and gives up (confirmed in the CI log). The healthcheck then asked for `localhost`, which under musl can resolve to ::1 first — a port nginx never bound. Probing 127.0.0.1 explicitly removes the ambiguity. Same reasoning already applied to the backend's --healthcheck implementation, which deliberately probes loopback rather than the configured bind host (which may be 0.0.0.0). Worth noting the failure mode: the service was WORKING the whole time. Only the probe was wrong — which is the most misleading kind of red, and exactly what a smoke test exists to surface before it confuses someone at 2am.
pacphi
added a commit
that referenced
this pull request
Aug 1, 2026
ci-build-optimization finished (8/8 phases, integration PR #213 merged), so its successor plan takes over as the active pipeline: an evidence-driven docs accuracy audit and remediation across 6 personas (end user, operator, API/MCP consumer, contributor, AI agent, decision reader), closing with a CI drift guardrail.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
CI/build/Docker optimization — 8 phases, ready for human review
Do NOT auto-merge. This is the human checkpoint;
mainis never merged autonomously.Implements
OPTIMIZATION_SPEC.md(from the rust-optimizer audit). 44 files, +2387/−1438.The headline: your published Docker image could not run
release.ymlhad notarget:, so buildx built each Dockerfile's last stage —development. Every release published a cargo-watch / node-dev-server image to ghcr as:latest, with no compiled binary, no migrations, no entrypoint. Demonstrated: building the frontend withouttarget:yields 207 MB with no nginx; withtarget: runtime, 69.8 MB with nginx.Fixing that exposed a chain of latent defects, none of which were in the spec, because the release image had never once been built:
ruvectorpath deps.dockerignore;backend/targetis ~74 GBRUST_VERSIONbuild arg silently ignored (noARG)./backend, so../ruvectorwas unreachablepkg-config/libssl-dev/g++/app/dataroot-owned under compose →Permission deniedDefect 6 is the one to understand.
rust:1.97-slimsilently migrated from bookworm to trixie, so the image built successfully, produced a 72 MB binary, and passed every grep-based check — while the binary died withversion `GLIBC_2.39' not found. Only running it caught that. Counter-intuitively the fix was to move the runtime up to trixie, not the builder down: ONNX Runtime ships prebuilt C++ objects needing GCC 13+, so bookworm cannot even link them.Measured CI improvements
just testruns 1177 tests / 15 binaries — exact parity withmake test, verified by execution rather than inference.Other substantive changes
make testand twoaudittargets could never fail; rootdownload-modelsexited 0 on real failure).rust-toolchain.toml, then de-duplicated into a composite action (6 copies → 1).lighthousewas non-blocking andbundlewatchonly echoed warnings; neither ever gated anything.qe-court: REMAND → fixed → SHIP
First properly-seated cross-vendor panel (GPT jury, Claude author;
writerIsNeverJurorsatisfied). It returned REMAND with two real charges my own reviews had missed:entrypoint.sh,migrations/,rust-toolchain.toml,ruvector) — packaging regressions could merge unverified.makecommands survived in the product onboarding UI, the CLI, and 2 scripts.Both fixed and independently re-verified as RESOLVED. Full record:
.autopilot/court/ci-build-optimization/integration.md. Kill/overturn rounds were not run, so this is short of the full ADR-124 protocol.pl-healthcheck-flag-unhandled--healthcheck, whichmain.rsdoesn't parse → it spawns a second server and the container never reports healthy. Needs amain.rschange; no phase covered it.pl-lighthouse-warn-only"warn", so the manual Lighthouse job still cannot fail. Flipping to"error"sets your quality bar.pl-frontend-image-needs-compose-to-runpl-test-debuginfo-tradeoffCARGO_PROFILE_TEST_DEBUG=0costsfile:linein CI backtraces. Easy revert if it bites.The court's own strongest case against merging: CI proves images build, not that the system runs. A container-start/health smoke test is the single highest-value thing still missing.
Breaking change for developers
justis now required (brew install just/cargo install just/mise use -g just).scripts/setup-prereqs.shchecks for it. No CI workflow, Dockerfile, husky hook, or package.json script invokedmake, so automation is unaffected.Automated by autopilot
ci-build-optimization· 8 phases + optimization pass · ready for human review