summaryrefslogtreecommitdiff
path: root/bin/main.ml
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-17 15:45:27 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-17 15:45:27 +0200
commitc06ebb7344b7f267c325b832632f7cd4e81939e7 (patch)
treec246bda5176d2add869e74be3a2e632e075ea97a /bin/main.ml
parentcba04b3033f23b5c2515c9e648f2b83344716224 (diff)
downloadcolitur-c06ebb7344b7f267c325b832632f7cd4e81939e7.tar.gz
colitur-c06ebb7344b7f267c325b832632f7cd4e81939e7.zip
feat(cli): install the runtime data, and resolve it in three ways
`dune install` produced a colitur that could not run: the binary locates its data relative to its own path, nothing installed the four .sexp files anywhere, and an installed colitur exited 2 unable to read sanctoral.sexp. `dune build @install` produced an empty tree. data/dune installs the four RUNTIME files into <prefix>/share/colitur/ef. Deliberately not the two allow-lists: those describe where colitur and a comparison oracle disagree, are read only by the differential and oracle tests, and are meaningless to a running colitur -- installing them would ship an assertion about lectio's and missalemeum's behaviour as though it were calendar data. No glob, on purpose: a glob would silently start shipping them again the moment one was renamed. Resolution now probes rather than computing one path and hoping. An installed prefix is tried first, the build tree second, and a candidate counts only if sanctoral.sexp is actually readable inside it -- so a failed or half-removed install falls through to a working tree instead of shadowing it and failing later with a per-file error. COLITUR_DATA_DIR overrides both and NEVER falls through. This was the one real design question and the first version got it wrong: it treated the override as just another candidate, and a deliberately bogus value produced a full, plausible, entirely un-flagged year computed off the build tree. Someone who names a directory has stated an intent, and silently calendaring off a different one is the silent substitution this project refuses everywhere else. It is now exit 2 naming the directory. Verified end to end, not inferred: installed to a scratch prefix, then ran the binary from an unrelated cwd with no build tree near it, and confirmed 2038-03-06 still resolves Perpetua and Felicitas through the Common route. All four paths exercised -- build tree, valid override, bogus override, and an empty share/ falling through. Environment reads stay confined to bin/. The kernel's contract forbids them and nothing below the CLI learns where the data came from; the loaders take a path.
Diffstat (limited to 'bin/main.ml')
-rw-r--r--bin/main.ml62
1 files changed, 56 insertions, 6 deletions
diff --git a/bin/main.ml b/bin/main.ml
index 01cdbf8..ca89a12 100644
--- a/bin/main.ml
+++ b/bin/main.ml
@@ -74,12 +74,62 @@ let temporal_report y =
_build/default/bin/main.exe up two directories and back down into data/
always finds both files, regardless of the caller's own cwd.
- Known limitation, not yet exercised by this project: a `dune install`-
- style deployment (executable copied to a prefix with no adjacent _build/
- default/data/) would need a different resolution strategy; there is no
- install story yet (README.md: `dune exec` only), so this is not a
- regression against anything this project currently supports. *)
-let data_dir () = Filename.dirname (Filename.dirname Sys.executable_name) ^ "/data/ef"
+ RESOLVED: the "known limitation" this comment used to end on -- that a
+ `dune install`-style deployment (executable copied to a prefix with no
+ adjacent _build/default/data/) had no resolution strategy, and would exit 2
+ unable to find sanctoral.sexp -- is now handled by probing candidates in
+ order rather than computing one path and hoping. data/dune installs the
+ four runtime files into <prefix>/share/colitur/ef/.
+
+ Two layouts are probed, and one override short-circuits both:
+
+ [COLITUR_DATA_DIR], when set and non-blank -- an explicit override. It
+ NEVER falls through: if it is set and does not contain the data, that is
+ an error naming the directory, not a reason to quietly use different
+ data. A packager or operator who names a directory has stated an
+ intent, and silently calendaring off some other copy because theirs was
+ wrong is precisely the silent substitution this project refuses
+ everywhere else (CLAUDE.md's first binding decision: divergence is
+ flagged LOUDLY, never silently swallowed). Getting this wrong is not
+ hypothetical -- the first version of this function did fall through, and
+ a deliberately bogus COLITUR_DATA_DIR produced a full, plausible,
+ entirely un-flagged year off the build tree's data.
+
+ Otherwise, in order:
+ 1. <exedir>/../share/colitur/ef -- the INSTALLED layout, from an opam or
+ `dune install` prefix where the binary sits at <prefix>/bin/colitur.
+ data/dune puts the four runtime files there.
+ 2. <exedir>/../data/ef -- the BUILD TREE, which is what `dune exec` and
+ the cram tests use.
+
+ A candidate is accepted only if sanctoral.sexp is actually readable inside
+ it, not merely because the directory exists: an empty or half-populated
+ share/colitur/ef (a failed install, a partially removed package) falls
+ through to a working build tree rather than shadowing it and then failing
+ at load time with a confusing per-file error. Verified by simulation, not
+ assumed.
+
+ Environment reads are fine HERE and only here: this is bin/, not the
+ kernel, whose contract forbids them (CLAUDE.md, "Kernel is total &
+ deterministic: no wall-clock, randomness, or environment reads"). Nothing
+ below the CLI ever learns where the data came from -- the loaders take a
+ path. *)
+let data_dir () =
+ let has_data d = Sys.file_exists (Filename.concat d "sanctoral.sexp") in
+ let prefix = Filename.dirname (Filename.dirname Sys.executable_name) in
+ let installed = List.fold_left Filename.concat prefix [ "share"; "colitur"; "ef" ] in
+ let build_tree = Filename.concat prefix (Filename.concat "data" "ef") in
+ match Sys.getenv_opt "COLITUR_DATA_DIR" with
+ | Some d when String.trim d <> "" ->
+ if has_data d then d
+ else begin
+ Printf.eprintf
+ "colitur: COLITUR_DATA_DIR is set to %s, which contains no sanctoral.sexp\n\
+ colitur: refusing to fall back to another data directory -- unset it, or point it at one\n"
+ d;
+ exit 2
+ end
+ | _ -> if has_data installed then installed else build_tree
(* Loads the universal sanctoral layer and applies the one hand-authored
overlay over it (data/ef/adjustments.sexp -- see that file's own header):