commits
Previously the JTW assembly assumed a single OCaml compiler version
across all solutions, picking it from the first solution. This broke
when solutions required different OCaml versions (e.g. containers.3.14
needs ocaml < 5.4, solving to 5.3.0, while most packages use 5.4.0).
Now each solution carries its own OCaml version: compute_jtw_solutions
extracts it per-solution, and assemble_jtw_output sets up compiler dirs
for each unique version. This eliminates "inconsistent assumptions"
errors when loading packages built with a different compiler.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace per-universe file copies with content-hashed paths under p/<pkg>/<ver>/<hash>/,
so identical package builds are stored once. Universe dirs now only contain a findlib_index
with relative paths into p/. Compiler dir also gains a content hash level for immutable
caching. Remove blessed/non-blessed distinction. Ensure deterministic output ordering
throughout (sorted modules, metas, find results).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Investigates the problem of finding compatible sets of pre-solved package
universes. Implements and benchmarks 6 CSP algorithms (brute force,
backtracking+FC, AC-3, greedy+MRV, signature clustering, fingerprint hashing).
Brute force with incremental map merging wins decisively at all practical scales.
Includes real-world validation against 18,388 solved universes (all versions of
all 4,491 opam packages): 63.7% pairwise compatible, with deep conflict
classification showing incompatibility is ecosystemic rather than caused by any
single dependency.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The sudo cp -a preserves container uid ownership, preventing
subsequent cmi/META copies and dynamic_cmis.json generation.
Chown the lib directory after the initial copy.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The permission-denied messages from the non-sudo rm are noise since
the sudo fallback handles them. Redirect stderr to /dev/null.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace non-existent 'jtw mk-backend' with 'jtw opam' which handles
both worker.js and stdlib generation in one step
- Pin and install js_top_worker-web (needed by worker.js build)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Layer files created inside containers are owned by root, so plain
rm -rf fails with permission denied. Fall back to sudo rm -rf when
the initial rm fails, matching the pattern used by Os.sudo_rm_rf.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The js_top_worker packages are published on the fork, not upstream.
Update default --jtw-tools-repo and --jtw-tools-branch accordingly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The jtw-tools layer needs js_top_worker which isn't on opam. Add
--jtw-tools-repo and --jtw-tools-branch flags (like doc-tools) so the
build script pins js_top_worker, js_top_worker-rpc, js_top_worker-bin,
and js_top_worker_rpc_def from the configured repo before installing.
The repo/branch are also included in the layer hash for correct
cache invalidation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enable ohc to produce JavaScript toplevel artifacts alongside
documentation, so every (package, version, universe) triple gets a
working browser-based OCaml REPL with that package pre-loaded.
Adds --with-jtw and --jtw-output flags to health-check and batch
commands. Creates jtw-tools layers (js_of_ocaml + js_top_worker per
OCaml version), per-package jtw layers (.cma.js, .cmi, META,
dynamic_cmis.json), and assembles output with compiler/, u/, and p/
directory structure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add lib/progress.ml with progress tracking types and atomic writes
- Write progress.json during batch runs (after solve, during builds)
- Dashboard now shows live progress: phase, targets, build/doc counts
- Live log viewer shows completed builds (finds logs via package symlinks)
- Auto-detect platform from cache directory for web server
- Add accessor functions to Run_log for progress tracking
The dashboard now displays real-time progress during batch runs including
the current phase (solving/building/gc), target list, and completion counts.
Live log viewer now works for both in-progress and completed builds.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Lock system enhancements:
- Add universe hash to build locks enabling parallel builds of same
package in different universes
- Add Tool stage for tracking odoc/odoc_driver tool builds
- Store layer_name and temp_log_path in lock files for live viewing
Live log viewer:
- New /live/:lock_file route with auto-refresh every 2 seconds
- Reads temp_log_path from lock file for in-progress builds
- Falls back to layer log for completed builds
- Auto-scroll toggle for monitoring build progress
Race condition fixes:
- Handle EEXIST on symlink creation in parallel worker scenarios
- Remove target before cp to prevent "File exists" errors
- Wrap universes.json atomic swap in try/catch
Source documentation support:
- Scan .ml and .mli files in addition to .cmt/.cmti for odoc_driver
- Enables source code documentation in generated docs
Other improvements:
- Add layer hash to doc log symlinks for uniqueness
- Ensure package symlinks created even for pre-existing layers
- Dashboard shows clickable locks for build/doc/tool stages
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace single symlink per package with directory structure:
packages/{pkg.version}/
build-{hash} -> ../../build-{hash} # all builds
doc-{hash} -> ../../doc-{hash} # all doc layers
blessed-build -> ../../build-{hash} # canonical if blessed
blessed-docs -> ../../doc-{hash} # canonical if blessed
This handles packages built multiple times with different deps:
- All builds are tracked via symlinks
- blessed-* symlinks mark the canonical build for docs
- Web dashboard checks blessed-build first, falls back to listing
Changes:
- bin/util.ml: New ensure_package_layer_symlink and
ensure_package_blessed_symlink functions
- bin/main.ml: Create doc layer symlinks and blessed symlinks
when doc generation succeeds
- web/data/layer_data.ml: Updated to use new directory structure,
checks blessed-build first
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The package detail page now determines build status from multiple sources:
1. Layer info (cache symlinks) - has exit_status and build timestamp
2. Run summary - check if package is in failures list
3. Log existence - if logs exist, package was processed
Status values:
- success: layer exit_status=0, or in run but not in failures
- failed: layer exit_status!=0, or in run failures list
- in_progress: run has no summary.json yet and build log exists
- pending: run in progress but no logs yet for this package
- unknown: no layer info and no logs found
Also shows log links only when logs actually exist, with clear
messaging when they don't.
Fixes issue where async.v0.17.0 showed "skipped" despite having logs.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Package authors need to see dependencies, reverse dependencies, and
log links even when data isn't available yet. Now:
- Build & Logs section always visible with status badge and log links
- Dependencies section always shown (with count and "not available" msg)
- Reverse Dependencies section always shown (with count)
- Log links shown when runs exist, otherwise clear "no logs yet" message
Previously these sections were hidden when data was missing, making
the page look incomplete and not discoverable.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Complete visual overhaul inspired by vintage scientific instrumentation
and CRT terminal displays:
- Typography: IBM Plex Mono for data, Space Grotesk for headings
- Color: Phosphor green (#00ff9d) primary with amber/red indicators
- Effects: Subtle CRT scan-lines, pulsing LED indicators, glow shadows
- Motion: Fade-in animations with staggered delays
- Layout: Instrument panel cards with inset data displays
- Details: Vignette overlay, hover glow effects, '>' list markers
The aesthetic matches the technical/analytical nature of a package
health checker while being visually distinctive and memorable.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add Dream.static handler for /docs/** route to serve generated odoc HTML.
Update docs links to point directly to /doc/index.html since Dream.static
doesn't serve directory indexes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add comprehensive package information that package authors need:
- Build status with success/failure badge
- Build timestamp showing when package was last built
- Direct links to build and doc logs for the latest run
- Dependencies list with clickable links to each dependency
- Reverse dependencies showing packages that depend on this one
- New /packages/:name/:version/logs route for combined log view
Implementation details:
- Add symlink creation in bin/main.ml when building layers
Creates packages/{pkg_str} -> ../build-{hash} for O(1) lookup
- Add layer_data module to read layer.json via symlinks
- Add parse_package_str helper for proper name.version parsing
(handles versions like 3.21.0 and v0.17.0 correctly)
This addresses the usability issues identified in package author review:
- Package authors can now see if their package is building
- Easy access to logs when debugging build failures
- Clear dependency information in both directions
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The previous percentage calculations were comparing apples to oranges:
- build_success (91 layers) / targets_requested (3) = 3033%
- doc_success (91 layers) / solutions_found (3) = 3033%
The confusion is that build_success/doc_success count ALL layers including
transitive dependencies, while targets_requested/solutions_found count
only the top-level packages requested.
Changed to show raw counts which are more meaningful:
- "Targets Solved: 3/3" (requested packages that solved)
- "Doc Layers OK: 91/91" (total doc layers built successfully)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
run_e2e.sh tests the full pipeline:
1. Solve a package (default: seq.0.3.1)
2. Build all dependencies
3. Generate docs with --with-doc
4. Verify outputs (summary.json, HTML, layers)
Usage:
./tests/integration/run_e2e.sh [--keep] [--package PKG]
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Run all 31 tests with: dune runtest
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use solutions_found/targets_requested for Solve Rate (not build_success)
- Use doc_success/solutions_found for Doc Rate
- Rename labels to be clearer: 'Solve Rate' and 'Doc Rate'
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add package browsing functionality with:
- List page showing all packages with searchable table
- Detail page showing package documentation link and version history
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add runs.ml view with three pages:
- list_page: Shows all runs with summary stats (builds, failures, docs)
- detail_page: Individual run with full metrics and failure table
- log_page: Build and doc log viewer with HTML escaping
Wire up routes in main.ml:
- /runs - Run history list
- /runs/:run_id - Run detail page
- /runs/:run_id/build/:package - Build log viewer
- /runs/:run_id/docs/:package - Doc log viewer
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Display overview stats including package count, build/doc success rates,
and latest run details with failure summary when available.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add package_data module for reading package information from the html
directory. Includes functions for listing packages, versions, and
checking if docs exist for specific packages.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add Day10_web_data.Run_data module for reading run information from day10's
log directory. Includes functions for listing runs, reading summaries,
and accessing build/doc logs.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add command-line options for day10-web:
- --cache-dir (required): Path to day10's cache directory
- --html-dir (required): Path to generated documentation directory
- --port/-p (default: 8080): HTTP port to listen on
- --host (default: 127.0.0.1): Host address to bind to
- --platform (default: debian-12-x86_64): Platform subdirectory in cache
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add day10-web package to the project with:
- New package stanza in dune-project with Dream dependency
- Minimal web server with Dream serving a basic HTML page
- Add explicit (package day10) to bin/dune to resolve multi-package ambiguity
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Detailed step-by-step plan for building the web frontend:
- 9 tasks with TDD approach
- Project setup with Dream
- Data layer (run_data, package_data) with 9 unit tests
- HTML layout and view modules
- Dashboard, Packages, and Runs pages
- Admin guide updates
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Design for day10-web, a status dashboard for package maintainers
and operators. Key decisions:
- Separate service reading day10's output directories
- OCaml with Dream web framework
- Server-rendered HTML with minimal JS
- Pages: Dashboard, Package list/detail, Run history/detail
- Dependency graph exploration (both directions)
- Public read-only, no authentication
- No database (filesystem as source of truth)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Covers:
- Prerequisites and installation
- Directory structure
- Basic and batch usage
- Production setup (systemd, cron, webhooks, nginx)
- Monitoring (logs, summary.json, disk usage)
- Maintenance (cache, GC, epoch transitions)
- Troubleshooting common issues
- Architecture overview
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds Run_log module for managing batch run logs:
- Timestamp-based run directories: runs/{YYYY-MM-DD-HHMMSS}/
- summary.json with run statistics and failure details
- Build logs symlinked to runs/{id}/build/{package}.log
- Doc logs symlinked to runs/{id}/docs/{package}.log
- 'latest' symlink updated after each run
Integration:
- Run started at beginning of batch processing
- Build/doc logs collected during summary generation
- Summary written with targets, solutions, successes, failures
Directory structure:
{log_dir}/
├── runs/
│ └── 2026-02-04-120000/
│ ├── summary.json
│ ├── build/*.log
│ └── docs/*.log
└── latest -> runs/2026-02-04-120000
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Mark completed features:
- Staging directory support with atomic swaps
- Failure preservation (graceful degradation)
- Garbage collection for layers and universes
Mark pending features:
- Epoch awareness
- Build/docs phase separation
- Webhook handler, Zulip notifier, log management
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
After a successful doc swap for blessed packages, write universes.json
containing the universe hash. This enables universe GC to determine
which universes are still referenced by blessed packages.
Format: {"universes": ["<32-char-hex-hash>"]}
Location: html/p/{package}/{version}/universes.json
The universes.json file moves atomically with the docs (same swap
mechanism), so failed rebuilds keep the old references alive.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
After each batch run, the GC now runs automatically as Phase 4:
- Layer GC: Deletes layers not referenced by current solutions
- Universe GC: Deletes universe directories not in any universes.json
Implementation:
- Added collect_referenced_layer_names to scan layer.json files and
match packages against current solutions
- Added run_gc function that runs layer and universe GC with reporting
- Integrated into both sequential and parallel batch processing branches
- Added day10_lib dependency to bin/dune
The GC reports statistics:
Phase 4: Running garbage collection...
Layers: N referenced, M deleted, K special kept
Universes: N referenced, M deleted
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Layer GC:
- gc_layers deletes unreferenced build/doc layers after each batch run
- Preserves special layers: base, solutions, doc-driver-*, doc-odoc-*
- Aggressive cleanup since regeneration is fast with layer caching
Universe GC:
- gc_universes scans html/p/*/*/universes.json to find referenced hashes
- Deletes universe directories in html/u/ that are no longer referenced
- Preserves universes until all their packages move to new universes
Implementation details:
- Uses regex to extract 32-character hex universe hashes from JSON
- List.sort_uniq for deduplication
- Comprehensive unit tests for both GC types
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
## Test Structure
Created a standalone testing library (day10_lib) with:
- lib/atomic_swap.ml: Self-contained atomic swap implementation
- lib/dune: Library definition
Added unit tests (tests/unit/):
- tests/unit/test_atomic_swap.ml: 9 test cases
- tests/unit/dune: Test executable definition
## Test Coverage
1. cleanup_stale_dirs:
- Removes .new directories (incomplete writes)
- Removes .old directories (incomplete swaps)
- Preserves normal version directories
2. get_swap_paths:
- Returns correct paths for blessed packages (html/p/{pkg}/{version})
- Returns correct paths for universe packages (html/u/{hash}/{pkg}/{version})
3. commit:
- Successfully swaps staging to final
- Replaces existing docs with new (via .old backup)
- Returns false when staging doesn't exist
4. rollback:
- Removes staging directory
- Preserves existing docs
## Design Decision: Standalone Library
Rather than testing the Os.Atomic_swap module directly (which has many
dependencies on opam libraries), created a standalone Atomic_swap module
with identical functionality but no external dependencies beyond Unix.
This allows tests to run quickly without building the full day10 stack.
The two implementations should be kept in sync; alternatively, the
Os.Atomic_swap module could be refactored to use this standalone version.
## Running Tests
dune build tests/unit/test_atomic_swap.exe
_build/default/tests/unit/test_atomic_swap.exe
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Calls Os.Atomic_swap.cleanup_stale_dirs at the start of run_batch to
remove any .new or .old directories left from interrupted swaps in
previous runs.
This ensures recovery from crashes: if a previous run was killed between
renaming operations, the next run will clean up the artifacts before
processing any packages.
Only runs if html_output is configured (batch mode with shared HTML dir).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit implements the core atomic swap mechanism for documentation
output, ensuring that existing working docs are never destroyed by a
failed rebuild.
## Key Changes
### bin/os.ml - Atomic_swap module
Added a new Atomic_swap module with:
- `cleanup_stale_dirs`: Scans html/p/ and html/u/ directories for .new
and .old artifacts left from interrupted swaps. Call on startup.
- `get_swap_paths`: Returns staging, final, and backup paths for a package
- `prepare_staging`: Creates the .new staging directory
- `commit`: Performs the atomic swap (old → .old, staging → final, rm .old)
- `rollback`: Cleans up staging on failure, leaving original intact
### bin/linux.ml - generate_docs changes
Modified the doc generation flow:
1. Create a temporary staging HTML directory for each package
2. Run odoc_driver_voodoo writing to staging instead of final location
3. On SUCCESS: Atomically swap staging to final using sudo mv
- If final exists, rename to .old first
- Move staging to final
- Remove .old backup
4. On FAILURE: Log "graceful degradation", clean up staging, keep old docs
5. Clean up staging directory regardless of outcome
## Design Decisions
1. **Staging per-package**: Each package gets its own temp staging dir
rather than a global staging area. This allows parallel workers to
operate independently without coordination.
2. **sudo mv for swap**: Container processes may create root-owned files
in the HTML output, so we use `sudo mv` for the final swap step.
3. **Inline swap logic**: Rather than using the Atomic_swap module functions
directly in generate_docs, the swap logic is inline because it needs to
handle the specific container ownership issues and logging.
## Known Limitations
- cleanup_stale_dirs is implemented but not yet called from run_batch
- Universe packages (.../u/{hash}/...) use same pattern as blessed (/p/...)
- Tests not yet added (next commit)
## Discovery: Staging Directory Approach
Initially considered modifying odoc_driver_voodoo to write to .new paths
directly, but this would require changes to the voodoo toolchain. Instead,
we intercept at the bind-mount level: mount a temp staging dir as /html,
let voodoo write normally, then move the output atomically.
This preserves compatibility with existing odoc_driver_voodoo behavior
while achieving the atomicity we need for graceful degradation.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
ocaml-zulip and its dependencies are not in opam-repository,
need to use https://tangled.org/anil.recoil.org/aoah-opam-repo
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Layer GC: aggressive cleanup after each run, delete any layer
not referenced by current solutions
- Universe GC: store universe refs in each package's docs directory
(universes.json), derive live universes from blessed packages,
delete unreferenced universe directories
The universe refs move atomically with the docs via the same .new/.old
swap mechanism, so failed rebuilds keep old universes alive.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tests the core "fresh solving" principle - when a new dependency
version becomes available in opam-repository, day10 should re-solve
and pick up the new version rather than caching solutions forever.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comprehensive testing strategy for day10 covering:
- Two-tier test execution (fast ~2min, full ~30-60min)
- Mini opam-repository fixtures for controlled testing
- Real repository snapshot tests for compatibility
- Fault injection via container resource limits
- Notification testing via abstraction layer
- Output validation for generated documentation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Gap analysis comparing day10 to ocaml-docs-ci for docs.ocaml.org migration:
- Feature comparison matrix
- Identified critical gaps (epochs, change detection)
- Noted that OCluster distribution is not needed (single machine in practice)
- Revised timeline: 16 weeks instead of 22
Fresh docs design ("always fresh, always safe"):
- Always solve against current opam-repository (no stale cross-refs)
- Atomic package-level updates via directory swap
- Epoch transitions for major structural changes
- Build and docs phases independent (doc failures don't block builds)
- Retry with backoff, fail fast on errors
- Webhook trigger + cron fallback
- Zulip notifications on failures
- Permanent log retention
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Random.self_init() wasn't providing enough uniqueness for workers
forked at nearly the same time. Now we explicitly seed with:
PID XOR (timestamp * 1000000)
This ensures each worker gets a unique random sequence.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Some packages (like conf-* packages) don't include an OCaml compiler
in their solution. Instead of failing with "No OCaml version found",
we now skip doc generation for these packages gracefully.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Solutions are now cached in <cache-dir>/solutions/<commit-hash>/<package>.json
This avoids re-running the solver when re-running batch with the same
opam-repository state.
Output now shows:
Phase 1: Solving 18341 targets (cache: abc1234, 15000 cached)...
X solutions loaded from cache, Y newly solved
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Reseed RNG after fork to prevent temp directory name collisions
- Increase layer wait timeout from 30 seconds to 5 minutes
(prevents "layer never completed" errors when builds take longer)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Silence print_build_result (was printing [NOTE] success etc)
- Remove "Running deferred doc link phase" messages
- Remove "Running Doc_link_only" messages
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove "Unknown variable" warnings for unhandled opam variables
- Redirect docker create/rm stdout to /dev/null (was printing container IDs)
- Remove print_string for docker export output
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix fork_with_progress to not call waitpid twice on same process
(was causing "Unix.Unix_error(Unix.ECHILD, waitpid)" crash)
- Redirect mount/umount stderr to /dev/null (suppresses fstab hints)
- Remove per-package warnings for doc tool layer failures
(these are recorded in layer.json, no need to spam terminal)
- Remove odoc_driver_voodoo exception stderr output
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Catch exceptions in forked workers and convert to exit code 1
- Track worker exit status (WEXITED/WSIGNALED/WSTOPPED)
- Report failed worker count in progress indicator
- Print "Batch complete: N targets, M failed" at end
This helps diagnose premature batch termination by showing if workers
crashed and providing a clear completion message.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove verbose per-layer "Build layer" and "Doc layer" messages
- Remove command logging from exec function
- Remove verbose copy_tree logging
- Redirect cp stderr to /dev/null to suppress file operation warnings
- Add fork_with_progress function for tracking parallel worker completion
- Add progress indicator during Phase 3: "[Phase 3] 500/1000 targets completed"
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add Os.fork_map function that runs a function in parallel across a list
and collects results via temp files. Use this to parallelize the solve
phase in run_batch, which was previously sequential.
Also add Util.solution_to_json/solution_of_json for string-based
serialization of solutions.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously the JTW assembly assumed a single OCaml compiler version
across all solutions, picking it from the first solution. This broke
when solutions required different OCaml versions (e.g. containers.3.14
needs ocaml < 5.4, solving to 5.3.0, while most packages use 5.4.0).
Now each solution carries its own OCaml version: compute_jtw_solutions
extracts it per-solution, and assemble_jtw_output sets up compiler dirs
for each unique version. This eliminates "inconsistent assumptions"
errors when loading packages built with a different compiler.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace per-universe file copies with content-hashed paths under p/<pkg>/<ver>/<hash>/,
so identical package builds are stored once. Universe dirs now only contain a findlib_index
with relative paths into p/. Compiler dir also gains a content hash level for immutable
caching. Remove blessed/non-blessed distinction. Ensure deterministic output ordering
throughout (sorted modules, metas, find results).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Investigates the problem of finding compatible sets of pre-solved package
universes. Implements and benchmarks 6 CSP algorithms (brute force,
backtracking+FC, AC-3, greedy+MRV, signature clustering, fingerprint hashing).
Brute force with incremental map merging wins decisively at all practical scales.
Includes real-world validation against 18,388 solved universes (all versions of
all 4,491 opam packages): 63.7% pairwise compatible, with deep conflict
classification showing incompatibility is ecosystemic rather than caused by any
single dependency.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The jtw-tools layer needs js_top_worker which isn't on opam. Add
--jtw-tools-repo and --jtw-tools-branch flags (like doc-tools) so the
build script pins js_top_worker, js_top_worker-rpc, js_top_worker-bin,
and js_top_worker_rpc_def from the configured repo before installing.
The repo/branch are also included in the layer hash for correct
cache invalidation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enable ohc to produce JavaScript toplevel artifacts alongside
documentation, so every (package, version, universe) triple gets a
working browser-based OCaml REPL with that package pre-loaded.
Adds --with-jtw and --jtw-output flags to health-check and batch
commands. Creates jtw-tools layers (js_of_ocaml + js_top_worker per
OCaml version), per-package jtw layers (.cma.js, .cmi, META,
dynamic_cmis.json), and assembles output with compiler/, u/, and p/
directory structure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add lib/progress.ml with progress tracking types and atomic writes
- Write progress.json during batch runs (after solve, during builds)
- Dashboard now shows live progress: phase, targets, build/doc counts
- Live log viewer shows completed builds (finds logs via package symlinks)
- Auto-detect platform from cache directory for web server
- Add accessor functions to Run_log for progress tracking
The dashboard now displays real-time progress during batch runs including
the current phase (solving/building/gc), target list, and completion counts.
Live log viewer now works for both in-progress and completed builds.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Lock system enhancements:
- Add universe hash to build locks enabling parallel builds of same
package in different universes
- Add Tool stage for tracking odoc/odoc_driver tool builds
- Store layer_name and temp_log_path in lock files for live viewing
Live log viewer:
- New /live/:lock_file route with auto-refresh every 2 seconds
- Reads temp_log_path from lock file for in-progress builds
- Falls back to layer log for completed builds
- Auto-scroll toggle for monitoring build progress
Race condition fixes:
- Handle EEXIST on symlink creation in parallel worker scenarios
- Remove target before cp to prevent "File exists" errors
- Wrap universes.json atomic swap in try/catch
Source documentation support:
- Scan .ml and .mli files in addition to .cmt/.cmti for odoc_driver
- Enables source code documentation in generated docs
Other improvements:
- Add layer hash to doc log symlinks for uniqueness
- Ensure package symlinks created even for pre-existing layers
- Dashboard shows clickable locks for build/doc/tool stages
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace single symlink per package with directory structure:
packages/{pkg.version}/
build-{hash} -> ../../build-{hash} # all builds
doc-{hash} -> ../../doc-{hash} # all doc layers
blessed-build -> ../../build-{hash} # canonical if blessed
blessed-docs -> ../../doc-{hash} # canonical if blessed
This handles packages built multiple times with different deps:
- All builds are tracked via symlinks
- blessed-* symlinks mark the canonical build for docs
- Web dashboard checks blessed-build first, falls back to listing
Changes:
- bin/util.ml: New ensure_package_layer_symlink and
ensure_package_blessed_symlink functions
- bin/main.ml: Create doc layer symlinks and blessed symlinks
when doc generation succeeds
- web/data/layer_data.ml: Updated to use new directory structure,
checks blessed-build first
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The package detail page now determines build status from multiple sources:
1. Layer info (cache symlinks) - has exit_status and build timestamp
2. Run summary - check if package is in failures list
3. Log existence - if logs exist, package was processed
Status values:
- success: layer exit_status=0, or in run but not in failures
- failed: layer exit_status!=0, or in run failures list
- in_progress: run has no summary.json yet and build log exists
- pending: run in progress but no logs yet for this package
- unknown: no layer info and no logs found
Also shows log links only when logs actually exist, with clear
messaging when they don't.
Fixes issue where async.v0.17.0 showed "skipped" despite having logs.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Package authors need to see dependencies, reverse dependencies, and
log links even when data isn't available yet. Now:
- Build & Logs section always visible with status badge and log links
- Dependencies section always shown (with count and "not available" msg)
- Reverse Dependencies section always shown (with count)
- Log links shown when runs exist, otherwise clear "no logs yet" message
Previously these sections were hidden when data was missing, making
the page look incomplete and not discoverable.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Complete visual overhaul inspired by vintage scientific instrumentation
and CRT terminal displays:
- Typography: IBM Plex Mono for data, Space Grotesk for headings
- Color: Phosphor green (#00ff9d) primary with amber/red indicators
- Effects: Subtle CRT scan-lines, pulsing LED indicators, glow shadows
- Motion: Fade-in animations with staggered delays
- Layout: Instrument panel cards with inset data displays
- Details: Vignette overlay, hover glow effects, '>' list markers
The aesthetic matches the technical/analytical nature of a package
health checker while being visually distinctive and memorable.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add comprehensive package information that package authors need:
- Build status with success/failure badge
- Build timestamp showing when package was last built
- Direct links to build and doc logs for the latest run
- Dependencies list with clickable links to each dependency
- Reverse dependencies showing packages that depend on this one
- New /packages/:name/:version/logs route for combined log view
Implementation details:
- Add symlink creation in bin/main.ml when building layers
Creates packages/{pkg_str} -> ../build-{hash} for O(1) lookup
- Add layer_data module to read layer.json via symlinks
- Add parse_package_str helper for proper name.version parsing
(handles versions like 3.21.0 and v0.17.0 correctly)
This addresses the usability issues identified in package author review:
- Package authors can now see if their package is building
- Easy access to logs when debugging build failures
- Clear dependency information in both directions
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The previous percentage calculations were comparing apples to oranges:
- build_success (91 layers) / targets_requested (3) = 3033%
- doc_success (91 layers) / solutions_found (3) = 3033%
The confusion is that build_success/doc_success count ALL layers including
transitive dependencies, while targets_requested/solutions_found count
only the top-level packages requested.
Changed to show raw counts which are more meaningful:
- "Targets Solved: 3/3" (requested packages that solved)
- "Doc Layers OK: 91/91" (total doc layers built successfully)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add runs.ml view with three pages:
- list_page: Shows all runs with summary stats (builds, failures, docs)
- detail_page: Individual run with full metrics and failure table
- log_page: Build and doc log viewer with HTML escaping
Wire up routes in main.ml:
- /runs - Run history list
- /runs/:run_id - Run detail page
- /runs/:run_id/build/:package - Build log viewer
- /runs/:run_id/docs/:package - Doc log viewer
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add command-line options for day10-web:
- --cache-dir (required): Path to day10's cache directory
- --html-dir (required): Path to generated documentation directory
- --port/-p (default: 8080): HTTP port to listen on
- --host (default: 127.0.0.1): Host address to bind to
- --platform (default: debian-12-x86_64): Platform subdirectory in cache
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Detailed step-by-step plan for building the web frontend:
- 9 tasks with TDD approach
- Project setup with Dream
- Data layer (run_data, package_data) with 9 unit tests
- HTML layout and view modules
- Dashboard, Packages, and Runs pages
- Admin guide updates
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Design for day10-web, a status dashboard for package maintainers
and operators. Key decisions:
- Separate service reading day10's output directories
- OCaml with Dream web framework
- Server-rendered HTML with minimal JS
- Pages: Dashboard, Package list/detail, Run history/detail
- Dependency graph exploration (both directions)
- Public read-only, no authentication
- No database (filesystem as source of truth)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Covers:
- Prerequisites and installation
- Directory structure
- Basic and batch usage
- Production setup (systemd, cron, webhooks, nginx)
- Monitoring (logs, summary.json, disk usage)
- Maintenance (cache, GC, epoch transitions)
- Troubleshooting common issues
- Architecture overview
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds Run_log module for managing batch run logs:
- Timestamp-based run directories: runs/{YYYY-MM-DD-HHMMSS}/
- summary.json with run statistics and failure details
- Build logs symlinked to runs/{id}/build/{package}.log
- Doc logs symlinked to runs/{id}/docs/{package}.log
- 'latest' symlink updated after each run
Integration:
- Run started at beginning of batch processing
- Build/doc logs collected during summary generation
- Summary written with targets, solutions, successes, failures
Directory structure:
{log_dir}/
├── runs/
│ └── 2026-02-04-120000/
│ ├── summary.json
│ ├── build/*.log
│ └── docs/*.log
└── latest -> runs/2026-02-04-120000
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Mark completed features:
- Staging directory support with atomic swaps
- Failure preservation (graceful degradation)
- Garbage collection for layers and universes
Mark pending features:
- Epoch awareness
- Build/docs phase separation
- Webhook handler, Zulip notifier, log management
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
After a successful doc swap for blessed packages, write universes.json
containing the universe hash. This enables universe GC to determine
which universes are still referenced by blessed packages.
Format: {"universes": ["<32-char-hex-hash>"]}
Location: html/p/{package}/{version}/universes.json
The universes.json file moves atomically with the docs (same swap
mechanism), so failed rebuilds keep the old references alive.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
After each batch run, the GC now runs automatically as Phase 4:
- Layer GC: Deletes layers not referenced by current solutions
- Universe GC: Deletes universe directories not in any universes.json
Implementation:
- Added collect_referenced_layer_names to scan layer.json files and
match packages against current solutions
- Added run_gc function that runs layer and universe GC with reporting
- Integrated into both sequential and parallel batch processing branches
- Added day10_lib dependency to bin/dune
The GC reports statistics:
Phase 4: Running garbage collection...
Layers: N referenced, M deleted, K special kept
Universes: N referenced, M deleted
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Layer GC:
- gc_layers deletes unreferenced build/doc layers after each batch run
- Preserves special layers: base, solutions, doc-driver-*, doc-odoc-*
- Aggressive cleanup since regeneration is fast with layer caching
Universe GC:
- gc_universes scans html/p/*/*/universes.json to find referenced hashes
- Deletes universe directories in html/u/ that are no longer referenced
- Preserves universes until all their packages move to new universes
Implementation details:
- Uses regex to extract 32-character hex universe hashes from JSON
- List.sort_uniq for deduplication
- Comprehensive unit tests for both GC types
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
## Test Structure
Created a standalone testing library (day10_lib) with:
- lib/atomic_swap.ml: Self-contained atomic swap implementation
- lib/dune: Library definition
Added unit tests (tests/unit/):
- tests/unit/test_atomic_swap.ml: 9 test cases
- tests/unit/dune: Test executable definition
## Test Coverage
1. cleanup_stale_dirs:
- Removes .new directories (incomplete writes)
- Removes .old directories (incomplete swaps)
- Preserves normal version directories
2. get_swap_paths:
- Returns correct paths for blessed packages (html/p/{pkg}/{version})
- Returns correct paths for universe packages (html/u/{hash}/{pkg}/{version})
3. commit:
- Successfully swaps staging to final
- Replaces existing docs with new (via .old backup)
- Returns false when staging doesn't exist
4. rollback:
- Removes staging directory
- Preserves existing docs
## Design Decision: Standalone Library
Rather than testing the Os.Atomic_swap module directly (which has many
dependencies on opam libraries), created a standalone Atomic_swap module
with identical functionality but no external dependencies beyond Unix.
This allows tests to run quickly without building the full day10 stack.
The two implementations should be kept in sync; alternatively, the
Os.Atomic_swap module could be refactored to use this standalone version.
## Running Tests
dune build tests/unit/test_atomic_swap.exe
_build/default/tests/unit/test_atomic_swap.exe
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Calls Os.Atomic_swap.cleanup_stale_dirs at the start of run_batch to
remove any .new or .old directories left from interrupted swaps in
previous runs.
This ensures recovery from crashes: if a previous run was killed between
renaming operations, the next run will clean up the artifacts before
processing any packages.
Only runs if html_output is configured (batch mode with shared HTML dir).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit implements the core atomic swap mechanism for documentation
output, ensuring that existing working docs are never destroyed by a
failed rebuild.
## Key Changes
### bin/os.ml - Atomic_swap module
Added a new Atomic_swap module with:
- `cleanup_stale_dirs`: Scans html/p/ and html/u/ directories for .new
and .old artifacts left from interrupted swaps. Call on startup.
- `get_swap_paths`: Returns staging, final, and backup paths for a package
- `prepare_staging`: Creates the .new staging directory
- `commit`: Performs the atomic swap (old → .old, staging → final, rm .old)
- `rollback`: Cleans up staging on failure, leaving original intact
### bin/linux.ml - generate_docs changes
Modified the doc generation flow:
1. Create a temporary staging HTML directory for each package
2. Run odoc_driver_voodoo writing to staging instead of final location
3. On SUCCESS: Atomically swap staging to final using sudo mv
- If final exists, rename to .old first
- Move staging to final
- Remove .old backup
4. On FAILURE: Log "graceful degradation", clean up staging, keep old docs
5. Clean up staging directory regardless of outcome
## Design Decisions
1. **Staging per-package**: Each package gets its own temp staging dir
rather than a global staging area. This allows parallel workers to
operate independently without coordination.
2. **sudo mv for swap**: Container processes may create root-owned files
in the HTML output, so we use `sudo mv` for the final swap step.
3. **Inline swap logic**: Rather than using the Atomic_swap module functions
directly in generate_docs, the swap logic is inline because it needs to
handle the specific container ownership issues and logging.
## Known Limitations
- cleanup_stale_dirs is implemented but not yet called from run_batch
- Universe packages (.../u/{hash}/...) use same pattern as blessed (/p/...)
- Tests not yet added (next commit)
## Discovery: Staging Directory Approach
Initially considered modifying odoc_driver_voodoo to write to .new paths
directly, but this would require changes to the voodoo toolchain. Instead,
we intercept at the bind-mount level: mount a temp staging dir as /html,
let voodoo write normally, then move the output atomically.
This preserves compatibility with existing odoc_driver_voodoo behavior
while achieving the atomicity we need for graceful degradation.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Layer GC: aggressive cleanup after each run, delete any layer
not referenced by current solutions
- Universe GC: store universe refs in each package's docs directory
(universes.json), derive live universes from blessed packages,
delete unreferenced universe directories
The universe refs move atomically with the docs via the same .new/.old
swap mechanism, so failed rebuilds keep old universes alive.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comprehensive testing strategy for day10 covering:
- Two-tier test execution (fast ~2min, full ~30-60min)
- Mini opam-repository fixtures for controlled testing
- Real repository snapshot tests for compatibility
- Fault injection via container resource limits
- Notification testing via abstraction layer
- Output validation for generated documentation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Gap analysis comparing day10 to ocaml-docs-ci for docs.ocaml.org migration:
- Feature comparison matrix
- Identified critical gaps (epochs, change detection)
- Noted that OCluster distribution is not needed (single machine in practice)
- Revised timeline: 16 weeks instead of 22
Fresh docs design ("always fresh, always safe"):
- Always solve against current opam-repository (no stale cross-refs)
- Atomic package-level updates via directory swap
- Epoch transitions for major structural changes
- Build and docs phases independent (doc failures don't block builds)
- Retry with backoff, fail fast on errors
- Webhook trigger + cron fallback
- Zulip notifications on failures
- Permanent log retention
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Solutions are now cached in <cache-dir>/solutions/<commit-hash>/<package>.json
This avoids re-running the solver when re-running batch with the same
opam-repository state.
Output now shows:
Phase 1: Solving 18341 targets (cache: abc1234, 15000 cached)...
X solutions loaded from cache, Y newly solved
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix fork_with_progress to not call waitpid twice on same process
(was causing "Unix.Unix_error(Unix.ECHILD, waitpid)" crash)
- Redirect mount/umount stderr to /dev/null (suppresses fstab hints)
- Remove per-package warnings for doc tool layer failures
(these are recorded in layer.json, no need to spam terminal)
- Remove odoc_driver_voodoo exception stderr output
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Catch exceptions in forked workers and convert to exit code 1
- Track worker exit status (WEXITED/WSIGNALED/WSTOPPED)
- Report failed worker count in progress indicator
- Print "Batch complete: N targets, M failed" at end
This helps diagnose premature batch termination by showing if workers
crashed and providing a clear completion message.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove verbose per-layer "Build layer" and "Doc layer" messages
- Remove command logging from exec function
- Remove verbose copy_tree logging
- Redirect cp stderr to /dev/null to suppress file operation warnings
- Add fork_with_progress function for tracking parallel worker completion
- Add progress indicator during Phase 3: "[Phase 3] 500/1000 targets completed"
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add Os.fork_map function that runs a function in parallel across a list
and collects results via temp files. Use this to parallelize the solve
phase in run_batch, which was previously sequential.
Also add Util.solution_to_json/solution_of_json for string-based
serialization of solutions.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>