Stage 0: CI & tooling foundation (fmt/clippy/test/wasm gate) - #1
Conversation
Add stage-overview plus per-stage plans (CI, core parse/validate, DAG layout, wasm viewer, serve+live-reload, hub deploy). First published release 0.1.0 is cut after Stage 4; Stage 5 ships as 0.1.1.
Incorporate accepted eng-review decisions into the Stage 0 CI plan: exact 1.94.1 toolchain pin, --locked builds + lockfile regeneration, drop unverified MSRV (root + per-crate), workflow on/concurrency/permissions, drop inert [workspace.lints], wasm-build ara-wasm too, SHA-pinned actions + ubuntu-24.04 + scoped Dependabot, and a branch-protection gate. Adds NOT-in-scope, failure-mode, parallelization, and implementation-task sections plus the review report. Track deferred work (T-MSRV, T-WASM-CLIPPY, T-DOCS) in TODOS.md.
Plan steps 1-3,5: exact toolchain pin (rust-toolchain.toml) + rustfmt config; remove rust-version from [workspace.package] and all 4 crate manifests; bump workspace 0.0.0 -> 0.0.1 and regenerate Cargo.lock so --locked CI stays in sync. (T2, T5)
Plan steps 4,6: fmt/clippy/test/wasm jobs on ubuntu-24.04 with --locked builds and SHA-pinned actions (checkout v7.0.0, rust-cache v2.9.1); on/concurrency/permissions top-matter; wasm job guards ara-core AND ara-wasm. Dependabot watches cargo + github-actions weekly. (T1,T3,T6,T7)
Plan step 8: document the CI-mirroring commands (fmt/clippy/test/wasm, all --locked) so contributors get a green PR on the first try.
fenfenai
left a comment
There was a problem hiding this comment.
Review Summary
Reviewed 20 files (+844/-10) across bug detection, guidelines compliance, and test/CI-correctness aspects. This is a config-only stage — no product logic — and it is well-executed: exact toolchain pin (1.94.1), SHA-pinned actions, --locked on every building job, least-privilege permissions: contents: read, and rustfmt using only stable options (max_width, edition) so it won't break on the stable channel. The Cargo.lock regen matches the 0.0.0 → 0.0.1 bump, so --locked stays green. The trivial dependency-free skeleton crates guarantee the fmt/clippy/test/wasm commands pass.
No findings at confidence ≥75 that block merge. Approving.
Minor (non-blocking)
Job name is wasm, but the CHANGELOG and PR body call it wasm-build (confidence: 80). The actual CI job id/name is wasm (.github/workflows/ci.yml:44-45), while CHANGELOG.md and the PR description list the gate as wasm-build. This is cosmetic today, but the deferred branch-protection step (plan step 7 / T8) selects required status checks by their exact job name — configuring protection off the docs would look for a non-existent wasm-build check. Suggest aligning the CHANGELOG/PR wording to wasm (or renaming the job) before T8 is set up.
Positive Observations
- Determinism rationale is captured inline (exact patch pin comment,
--lockedintent) — the why survives. - Deferred work (MSRV job, wasm-target clippy,
docs/lifecycle, branch protection) is documented inTODOS.mdrather than silently dropped. - Dependabot correctly covers both
cargoandgithub-actions, so the SHA pins won't rot silently.
| the defensive `bara-*` placeholders (`bara-core`, `bara-cli`, `bara-wasm`, | ||
| `bara-viewer`). | ||
| - Root README documenting the workspace layout and install path. | ||
| - CI workflow (`fmt`, `clippy`, `test`, `wasm-build`) on GitHub Actions with a |
There was a problem hiding this comment.
The CI job is named wasm (.github/workflows/ci.yml:44-45), not wasm-build. Minor, but the deferred branch-protection ruleset (plan T8) requires status checks by exact name, so align this to wasm to avoid selecting a non-existent check later. (confidence: 80)
All 6 plans in plans/ were for features that have shipped, so per CLAUDE.md's plan→docs lifecycle they move to docs/ as design records and out of plans/. - New design docs: stage-0-ci-tooling, stage-2-dag-layout, real-corpus-no-panic - Stage 0 (#1), Stage 2 (#5), issue #3 (#4) had no docs/ counterpart → written - issue #9 layout modes already folded into docs/stage-3-viewer.md → just removed - ara-runtime-impl-plan + stage-overview were meta-planning artifacts fully realized in code + per-stage docs → removed - Retire completed TODOs T-DOCS (this migration) and T-VIEWER-DIST-PACKAGING (resolved by the Stage 4 include_dir! embed), and the obsolete T-STAGE4-VERSION-BUMP (Stage 3/4 both shipped) - Fix dangling plan references in README.md and TODOS.md T-MSRV
) * docs(plans): retire shipped issue-7 plan, refresh Stage 5 hub-deploy plan Remove plans/issue-7-tree-list-mode.md — shipped in #14 (0.1.2) and folded into docs/stage-3-viewer.md + docs/manifest-schema.md. Refresh Stage 5 plan against the shipped Stage 4 code: correct the version target (0.1.2 -> 0.1.3), resolve hub routing (path-based /a/{id}/ with document-base-relative viewer URLs) and Docker assets (embedded-only), drop the now-moot precompressed-assets step, and add a --host flag + broken-ARA skip-on-ingest. * docs(plans): fold Stage 5 eng review findings into hub-deploy plan Eng review (14 findings + outside voice) folded into the Stage 5 plan: Docker needs no wasm toolchain (bundle committed + baked via include_dir!), harden base-href splice + id charset, no SPA fallback at hub root, share serve_cached_manifest, define empty/bad-root startup, clap ArgGroup for mode select, 308 (not cacheable 301) for known ids only, drop unused parsed Manifest on the hub path, wire --assets end-to-end, add wasm + headless browser tests for D1. Defer per-ARA figures (ServeDir cannot nest under a {id} param) and record the static-export alternative (D3: keep server). Add T-HUB-FIGURES and T-STATIC-EXPORT to TODOS.md. * feat(viewer): resolve API/live URLs relative to document base Plan step 1/4: Viewer relative API/WS URLs (T5) Make ManifestSource default URLs relative (api/manifest, api/live) so they resolve against document.baseURI — local serve unchanged (page at /), hub serves under <base href="/a/{id}/">. Refactor the ws:// scheme-swap core into a pure, wasm-testable ws_url_from_base(base, path). Native test guards the relative defaults; wasm test proves resolution for root and /a/{id}/ bases. Note: viewer source changed -> embed regen follows in step 4. * feat(serve): hub ingest cache — parse-once, immutable, id-guarded Plan step 2/4: Hub cache ingest (T2, T8, T12) Add serve/hub.rs: ingest(root) scans immediate child dirs into an immutable Arc<HashMap<id, Arc<CachedAra>>> (lock-free reads, no watcher). Ids constrained to [A-Za-z0-9._-]+ (rejects spaces/non-ASCII/slash/..) — one guard covering path-segment, URL, and HTML-escape safety. Broken ARAs and duplicate ids are logged/skipped, never fatal; an unreadable root is fatal (Err). Per-entry read errors are skipped, not dropped. cache.rs: manifest field is now Option<Arc<Manifest>>; from_dir_lean() drops the parsed graph the hub never reads (~2x resident memory saved per ARA) while from_dir() keeps it for local serve. Shared build() produces identical json + etag + figures_dir either way. Native tests cover: two good, one broken skipped, empty->empty, missing->Err, rejected charset, duplicate id kept-first, lean drops manifest. * feat(serve): hub router + CLI mode selection Plan step 3/4: Hub routing + CLI args (T3, T4, T7, T9, T10, T11, T13) - clap ArgGroup models local (positional dir) vs hub (--hub --ara-root) so bad combos fail at parse time; add --host (default 127.0.0.1, honors 0.0.0.0 for containers) (T9). - Extract serve_cached_manifest(&CachedAra, &HeaderMap) shared by the local and per-ARA hub manifest handlers — one conditional-GET source of truth (T7). - build_hub_router: /a/{id}/api/manifest (404 if unknown), /a/{id}/ index with <base href> injected + no-cache, / index listing, strict shared-asset fallback. No /api/live, no watcher. - Base-href splice guarded: no <head> -> 500, never a silent base-less page (T3). - Root asset serves real files only, else 404 — no SPA fallback (T4). - Bare /a/{id}: known -> 308, unknown -> 404 (never 308-to-404) (T10). - --assets wired end-to-end in hub (on-disk index + shared ServeDir) (T13). - hub oneshot tests incl. two sequential reads = same etag (pure cache hit, T11) + full parse-arg matrix; also applies pending rustfmt to cache.rs/hub.rs. run() branches to hub when --hub is set; local path behavior unchanged. * build: Docker image, deploy docs, CI smoke test, embed regen, 0.1.3 Plan step 4/4: Regen embed + Dockerfile + CI + docs (T1, T6, T14) - Regenerate the embedded viewer bundle (step 1 changed viewer source, so viewer-embed-fresh required a regen). - Dockerfile: multi-stage musl -> distroless, cargo-chef dep cache, NO wasm toolchain (viewer baked in via include_dir!). Pins the repo toolchain before adding the musl target and points CC/linker at musl-gcc. Verified: image builds, 3.1 MB compressed (< 20 MB budget) (T1). - .dockerignore keeps the context small (target/, corpus, git, plans/docs). - docs/deploy.md: hub routing, ingest, Docker/compose, systemd, Caddy/nginx reverse-proxy compression, and the manifest.json-fallback-inert-on-hub note (T14). Folds the plan into docs per CLAUDE.md. - CI: docker job (build + image-size budget + container smoke test: manifest 200/etag/304, /a/{id}/ html+base, wasm application/wasm). - viewer wasm test: relative api/manifest resolves under <base href="/a/x/"> in a real browser (T6). - Bump workspace 0.1.2 -> 0.1.3 + ara-core pins; CHANGELOG [Unreleased]. * docs: retire stage-5 plan (folded into docs/deploy.md) Plan fully implemented across the 4 preceding commits; per CLAUDE.md the plan is rewritten as a design doc (docs/deploy.md) and removed from plans/. * fix(docker): link the musl binary statically for distroless The CI docker job failed with 'exec /ara: no such file or directory'. The CARGO_TARGET_..._LINKER=musl-gcc override made musl-gcc the link driver, which links dynamically against /lib/ld-musl-x86_64.so.1 — absent from distroless/static, so the loader-less image can't exec the binary. Drop the linker override (rustc's musl target links fully static via its self-contained startup objects) and pin RUSTFLAGS=-C target-feature=+crt-static. Keep only the CC=musl-gcc override, which is what actually fixed the build script's -m64 error. Verified: the extracted binary is static-pie with no interpreter, and the container passes the full smoke test locally. * style(design): FINDING-001/004 — fix WCAG AA contrast for muted + accent text - --muted: #90856f → #726751 (3.38:1 → 5.16:1 on --bg, 3.10:1 → 4.75:1 on --panel2) - Add --accent-text: #8c4414 for text-on-light uses (3.67:1 → 6.60:1 on --bg) - Original --accent preserved for borders/backgrounds where 3:1 suffices * style(design): FINDING-002 — add focus-visible ring to tree-list rows Tree rows are role="button" + tabindex="0" but had no visible keyboard focus indicator. Adds a 2px accent outline matching the graph-mode node focus style. * style(design): FINDING-003 — respect prefers-reduced-motion Disable skeleton shimmer animation when the user's OS has motion reduction enabled. The only animation in the viewer is this loading shimmer. * style(design): FINDING-005/006 — enlarge touch targets, bump body font - Controls: height 1.75rem → min-height 2.25rem (28px → 34-36px) - Body font-size: 14px → 15px (closer to the 16px accessibility guideline while preserving data-dense tool proportions) - Applies to .toolbar-search/.toolbar-select, .layout-toggle, and .btn * style(design): FINDING-007 — use color-mix for --warn strikethrough Replace raw rgba(162, 59, 45, 0.4) with color-mix(in srgb, var(--warn) 40%, transparent) so the decoration color tracks the token if --warn changes. Matches the same technique used in the status pills. * build: regenerate embedded viewer bundle for design fixes The design-review CSS fixes (FINDING-001..007) changed the ara-viewer source, so `ara serve` shipped stale UI when run without --assets. Regenerated via scripts/embed-viewer.sh so the embedded bundle matches the source and CI's viewer-embed-fresh check passes. * docs: fold shipped plans into docs/, remove plans/ All 6 plans in plans/ were for features that have shipped, so per CLAUDE.md's plan→docs lifecycle they move to docs/ as design records and out of plans/. - New design docs: stage-0-ci-tooling, stage-2-dag-layout, real-corpus-no-panic - Stage 0 (#1), Stage 2 (#5), issue #3 (#4) had no docs/ counterpart → written - issue #9 layout modes already folded into docs/stage-3-viewer.md → just removed - ara-runtime-impl-plan + stage-overview were meta-planning artifacts fully realized in code + per-stage docs → removed - Retire completed TODOs T-DOCS (this migration) and T-VIEWER-DIST-PACKAGING (resolved by the Stage 4 include_dir! embed), and the obsolete T-STAGE4-VERSION-BUMP (Stage 3/4 both shipped) - Fix dangling plan references in README.md and TODOS.md T-MSRV * docs: drop dangling plan references in README and TODOS Point README at docs/ instead of the removed master plan, refresh T-MSRV context to the crates.io publish, and retire completed/obsolete TODOs (T-DOCS, T-VIEWER-DIST-PACKAGING, T-STAGE4-VERSION-BUMP).
Summary
Implements
plans/stage-0-ci-tooling.md.Adds the reproducible CI gate for the workspace before any product code lands: a
GitHub Actions workflow (
fmt,clippy,test,wasm-build) on a pinned,SHA-locked toolchain and action set, plus supporting config. No product logic;
this stage installs the quality gate every later stage PR runs through.
Plan Steps
1.94.1+ rustfmt config (b4e8f68)rust-version) from workspace + all 4 crates (b4e8f68)0.0.0 → 0.0.1and regenerateCargo.lockso--lockedstays in sync (b4e8f68)on/concurrency/permissions,ubuntu-24.04,--lockedbuilds, SHA-pinned actions (94c70b9)ara-coreandara-wasm(94c70b9)cargo+github-actions, weekly (94c70b9)CONTRIBUTING.mddocumenting the CI-mirroring local checks (5f3ac00)TODOS.mdfor deferred work — MSRV job, wasm-target clippy,docs/(e792e42)Deferred to a follow-up (documented, not silently dropped):
mainrequiring the 4 checks (plan step 7 / T8).This is a repo setting, and GitHub can only select the check names after CI
runs once — so it's a post-merge step, done once this PR's CI is green.
Review Status
Eng Review: CLEAR (
/plan-eng-review, 8 findings folded, 0 critical gaps).Outside voice (Codex) ran — surfaced the floating toolchain pin, lockfile/version
sequencing, unenforced gate, and ara-wasm coverage; all folded. See the
## GSTACK REVIEW REPORTin the plan file. Implementation independentlyspec-reviewed (all 8 items PASS).
Test Plan
All four CI commands were run locally and pass (they mirror the workflow exactly):
cargo fmt --all --checkcargo clippy --workspace --all-targets --locked -- -D warningscargo test --workspace --lockedcargo build -p ara-core -p ara-wasm --target wasm32-unknown-unknown --lockedmainbranch-protection ruleset (T8).