Browse Source

Canon in the deployment slot: res: provides + slot fallback

The canon never reached a usmrad-deployed slot: MANIFEST.usm provided
only bin:clanker-saddle, and usm's manifest install phase copies ONLY
the declared provides resources from the staged meson tree into the
install root - so the slot carried the binary and nothing else, and
Canon.directory() (which resolved the system path, the exe-relative
share and the source tree only) found no canon.

Two halves, per review direction:

- MANIFEST.usm now provides each canon .md as
  res:clanker-saddle/canon/<file>.md (the resource grammar usm's own
  autoprovides emits for share data), so the install phase copies the
  15 texts to <install-root>/usr/share/clanker-saddle/canon inside the
  slot.
- Canon.candidates() falls back to the deployment slot's data
  directory after the system shares: <appdir>/{current,a,b}/usr/share/
  clanker-saddle/canon, anchored on $USMRAD_APPDIR (default /app)
  because /proc/self/exe resolves through the flipped current link.
  Duplicates of the absolute system shares are dropped so the system
  path keeps precedence when it exists.
saddle-Clanker-Saddle 4 tuần trước cách đây
mục cha
commit
c9bd721c56
2 tập tin đã thay đổi với 90 bổ sung và 21 xóa
  1. 20 3
      MANIFEST.usm
  2. 70 18
      src/Services/CanonService.vala

+ 20 - 3
MANIFEST.usm

@@ -1,11 +1,28 @@
 {
   "name": "clanker-saddle",
   "version": "1.1",
-  "summary": "ClankerSaddle — LLM task orchestration platform (Statum + Spry)",
+  "summary": "ClankerSaddle \u2014 LLM task orchestration platform (Statum + Spry)",
   "licences": [],
-  "flags": ["ninjaStyleProgress"],
+  "flags": [
+    "ninjaStyleProgress"
+  ],
   "provides": {
-    "bin:clanker-saddle": "as-expected"
+    "bin:clanker-saddle": "as-expected",
+    "res:clanker-saddle/canon/gnu-manifesto.md": "as-expected",
+    "res:clanker-saddle/canon/free-software-definition.md": "as-expected",
+    "res:clanker-saddle/canon/open-source-misses-the-point.md": "as-expected",
+    "res:clanker-saddle/canon/right-to-read.md": "as-expected",
+    "res:clanker-saddle/canon/selling-free-software.md": "as-expected",
+    "res:clanker-saddle/canon/selling-exceptions.md": "as-expected",
+    "res:clanker-saddle/canon/words-to-avoid.md": "as-expected",
+    "res:clanker-saddle/canon/misinterpreting-copyright.md": "as-expected",
+    "res:clanker-saddle/canon/university-and-the-web.md": "as-expected",
+    "res:clanker-saddle/canon/plato-apology.md": "as-expected",
+    "res:clanker-saddle/canon/plato-republic-vi.md": "as-expected",
+    "res:clanker-saddle/canon/aristotle-nicomachean-ethics-i-ii.md": "as-expected",
+    "res:clanker-saddle/canon/aquinas-treatise-on-happiness.md": "as-expected",
+    "res:clanker-saddle/canon/lewis-abolition-of-man-i-ii.md": "as-expected",
+    "res:clanker-saddle/canon/magnifica-humanitas-summary.md": "as-expected"
   },
   "depends": {
     "runtime": [

+ 70 - 18
src/Services/CanonService.vala

@@ -184,9 +184,11 @@ namespace ClankerSaddle.Services {
          * absolute system shares (/usr/share, /usr/local/share — the
          * packaged layout where /proc/self/exe's neighbours are not
          * the install prefix, e.g. a container image with the binary
-         * copied elsewhere), then the source tree (<exe>/../../canon,
-         * <exe>/../canon, ./canon). Empty when nothing resolves — the
-         * endpoints then report an absent canon.
+         * copied elsewhere), then the deployment slot's data
+         * directory (the usmrad layout — see {@link slot_candidates}),
+         * then the source tree (<exe>/../../canon, <exe>/../canon,
+         * ./canon). Empty when nothing resolves — the endpoints then
+         * report an absent canon.
          */
         public string directory() {
             if (directory_cache != null) {
@@ -216,22 +218,72 @@ namespace ClankerSaddle.Services {
          * where the app looked.
          */
         public string[] candidates() {
+            var found = new string[0];
             var exe_dir = exe_directory();
-            return {
-                // installed: <prefix>/bin/clanker-saddle + share/…
-                GLib.Path.build_filename(exe_dir, "..", "share",
-                    "clanker-saddle", "canon"),
-                // packaged layout where the binary's neighbours are not
-                // the install prefix: the absolute system shares
-                "/usr/share/clanker-saddle/canon",
-                "/usr/local/share/clanker-saddle/canon",
-                // build dir run (builddir sits beside src/)
-                GLib.Path.build_filename(exe_dir, "..", "..", "canon"),
-                GLib.Path.build_filename(exe_dir, "..", "canon"),
-                GLib.Path.build_filename(exe_dir, "canon"),
-                // a bare ./canon next to the cwd (tests, dev runs)
-                "canon"
-            };
+            // installed: <prefix>/bin/clanker-saddle + share/…
+            found += GLib.Path.build_filename(exe_dir, "..", "share",
+                "clanker-saddle", "canon");
+            // packaged layout where the binary's neighbours are not
+            // the install prefix: the absolute system shares
+            found += "/usr/share/clanker-saddle/canon";
+            found += "/usr/local/share/clanker-saddle/canon";
+            // the usmrad deployment slot (deduped against the system
+            // shares above so the system path keeps its precedence)
+            foreach (var slot in slot_candidates()) {
+                found += slot;
+            }
+            // build dir run (builddir sits beside src/)
+            found += GLib.Path.build_filename(exe_dir, "..", "..", "canon");
+            found += GLib.Path.build_filename(exe_dir, "..", "canon");
+            found += GLib.Path.build_filename(exe_dir, "canon");
+            // a bare ./canon next to the cwd (tests, dev runs)
+            found += "canon";
+            return found;
+        }
+
+        /**
+         * The canon directories of the usmrad DEPLOYMENT SLOT layout.
+         *
+         * usmrad (the USM radio: a git-tracking deployer) clones the
+         * tracked branch to <appdir>/repo, builds it, and installs
+         * the manifest's provided resources into one of two A/B
+         * slots — <appdir>/<slot>/usr/… — then flips a `current`
+         * symlink onto the fresh slot and launches the binary
+         * through it. The canon rides that install as
+         * <appdir>/<slot>/usr/share/clanker-saddle/canon (the
+         * manifest provides each canon file as a res: resource, so
+         * usm's install phase copies it beside the binary).
+         *
+         * The candidates are built from $USMRAD_APPDIR (default
+         * /app): the `current` link first (the running deployment),
+         * then the concrete a/b slots (a restart racing a flip).
+         * Candidates that duplicate one of the absolute system
+         * shares are dropped — the system path keeps its precedence
+         * and its place in the candidates() order, so a canon
+         * installed at the system path is never shadowed by a slot
+         * copy. The exe's own directory is deliberately NOT an
+         * anchor: /proc/self/exe resolves through the `current`
+         * link, whose `..` climb lands in the appdir, not the slot.
+         */
+        private string[] slot_candidates() {
+            var found = new string[0];
+            var seen = new GLib.GenericSet<string>(GLib.str_hash, GLib.str_equal);
+            seen.add("/usr/share/clanker-saddle/canon");
+            seen.add("/usr/local/share/clanker-saddle/canon");
+            var appdir = (GLib.Environment.get_variable("USMRAD_APPDIR") ?? "")
+                .strip();
+            if (appdir.length == 0) {
+                appdir = "/app";
+            }
+            foreach (var slot in new string[] {"current", "a", "b"}) {
+                var candidate = GLib.Path.build_filename(
+                    appdir, slot, "usr", "share", "clanker-saddle", "canon");
+                if (!seen.contains(candidate)) {
+                    seen.add(candidate);
+                    found += candidate;
+                }
+            }
+            return found;
         }
 
         /**