summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bin/main.ml62
-rw-r--r--data/dune23
-rw-r--r--test/cli.t20
3 files changed, 99 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):
diff --git a/data/dune b/data/dune
new file mode 100644
index 0000000..07e5307
--- /dev/null
+++ b/data/dune
@@ -0,0 +1,23 @@
+; The four RUNTIME data files, installed into <prefix>/share/colitur/ef/ so a
+; `dune install`ed / opam-installed `colitur` can find them (bin/main.ml's own
+; [data_dir], which probes the installed layout before the build-tree one).
+;
+; Deliberately NOT the two allow-lists (expected-divergences.sexp,
+; expected-divergences-missalemeum.sexp): those are test fixtures describing
+; where colitur and a comparison oracle disagree, read only by
+; test_differential.ml / test_oracle.ml, 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.
+;
+; If a new runtime data file is ever added, it must be added HERE too -- there
+; is no glob, on purpose: a glob would silently start shipping the allow-lists
+; the moment someone renamed one, and an omission here fails loudly at startup
+; (exit 2, naming the missing file) rather than silently degrading.
+(install
+ (section share)
+ (package colitur)
+ (files
+ (ef/sanctoral.sexp as ef/sanctoral.sexp)
+ (ef/adjustments.sexp as ef/adjustments.sexp)
+ (ef/lectionary.sexp as ef/lectionary.sexp)
+ (ef/commons.sexp as ef/commons.sexp)))
diff --git a/test/cli.t b/test/cli.t
index 8de4591..288356c 100644
--- a/test/cli.t
+++ b/test/cli.t
@@ -191,3 +191,23 @@ A year outside the supported domain is rejected (exit 2):
$ colitur readings 1000
colitur: year 1000 out of range 1583..9999
[2]
+
+An explicit COLITUR_DATA_DIR that does not hold the data is an error (exit 2),
+never a silent fall-back to some other copy. A packager or operator who names
+a directory has stated an intent; quietly calendaring off a different one
+because theirs was wrong is the silent substitution this project refuses
+everywhere else. The first version of this resolution DID fall through, and a
+deliberately bogus value produced a full, plausible, entirely un-flagged year:
+
+ $ COLITUR_DATA_DIR=/nonexistent/xyz colitur readings 2026
+ colitur: COLITUR_DATA_DIR is set to /nonexistent/xyz, which contains no sanctoral.sexp
+ colitur: refusing to fall back to another data directory -- unset it, or point it at one
+ [2]
+
+An empty or half-populated installed directory is a different case and DOES
+fall through to the build tree: nobody stated an intent there, and a failed or
+partially removed install must not shadow a working tree and then fail later
+with a confusing per-file error. (Exercised directly in the task, by creating
+_build/default/share/colitur/ef and confirming the year still resolves; not
+reproduced here because the cram sandbox's own exe path makes the layout
+awkward to stage without asserting on dune internals.)