Explorar el Código

Add the five Invercargill-family project pages and the docs template

developer/{invercargill,invercargill-json,inversion,invercargill-sql,invercargill-sql-inversion}/index.html: one shared structure the later waves follow — masthead with Developer current, breadcrumb, docs-header facts plate (version from MANIFEST.usm, licence, package, language), marginal sticky contents rail, Overview/Features/Installation/Usage sections with code plates carrying per-snippet source attributions, a package ledger, sibling links and a prev/up/next pager.

All code examples are mined from the repos themselves (READMEs, examples/demo.vala, integration tests, Sql-Inversion's three source files); nothing is invented. Installation shows the real usm install names and each repo's actual meson commands; source availability is stated honestly with the repository name and forge, no fabricated web URL.

style.css gains the docs component set (docs-header, docs-layout, docs-toc, docs-prose, code-plate, docs-features, docs-package, docs-note, docs-pager, docs-siblings) with responsive collapse at 60/38rem. Explicit grid placement keeps the prose column on the wide track; inline code wraps anywhere so tokens cannot overflow the ledger.

check.py: the five landed pages leave the UPCOMING_PAGES allowlist (they are now enforced); the six A4/A5 pages stay listed for their tasks.
saddle-ClankerSaddle hace 2 semanas
padre
commit
0e0c62645b

+ 398 - 0
assets/css/style.css

@@ -1151,6 +1151,368 @@ a:hover {
   text-underline-offset: 3px;
 }
 
+/* ---------------------------------------------------------------------------
+   Project documentation pages — the developer library template (A3)
+   Structure: breadcrumb > project header plate > docs layout (prose +
+   marginal contents) > code plates > ledger of facts > pager. Marginalia
+   collapse under 60rem; code plates scroll rather than wrap.
+--------------------------------------------------------------------------- */
+
+/* Project header — the library's title plate */
+
+.docs-header {
+  padding-block: clamp(2.25rem, 5vw, 3.5rem) clamp(1.75rem, 4vw, 2.5rem);
+  text-align: center;
+}
+
+.docs-header h1 {
+  margin-top: 0.9rem;
+}
+
+.docs-header .lede {
+  max-width: 44rem;
+  margin: 1rem auto 0;
+}
+
+.docs-facts {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: center;
+  gap: 0.4rem 1.25rem;
+  margin-top: 1.6rem;
+  font-size: 0.88rem;
+  font-style: italic;
+  color: var(--ink-soft);
+}
+
+.docs-facts dt {
+  display: inline;
+  font-style: normal;
+  font-family: "Cinzel", serif;
+  font-size: 0.66rem;
+  font-weight: 600;
+  letter-spacing: 0.12em;
+  text-transform: uppercase;
+  color: var(--gold-ink);
+  margin-right: 0.35rem;
+}
+
+.docs-facts dd {
+  display: inline;
+  margin: 0;
+}
+
+.docs-facts code {
+  font-size: 0.9em;
+}
+
+/* The page body: prose column with a margin of contents */
+
+.docs-layout {
+  display: grid;
+  grid-template-columns: minmax(0, 1fr) 13.5rem;
+  gap: clamp(2rem, 5vw, 3.5rem);
+  align-items: start;
+}
+
+.docs-toc {
+  grid-column: 2;
+  grid-row: 1;
+  position: sticky;
+  top: 5.5rem;
+  padding: 1rem 1.1rem 1.2rem;
+  background: var(--surface);
+  border: 1px solid var(--line);
+}
+
+.docs-toc h2 {
+  font-family: "Cinzel", serif;
+  font-size: 0.68rem;
+  font-weight: 600;
+  letter-spacing: 0.14em;
+  text-transform: uppercase;
+  color: var(--gold-ink);
+}
+
+.docs-toc ul {
+  margin: 0.6rem 0 0;
+  padding: 0;
+  list-style: none;
+  font-size: 0.92rem;
+}
+
+.docs-toc li + li {
+  margin-top: 0.3rem;
+}
+
+.docs-toc a {
+  color: var(--ink-soft);
+  text-decoration: none;
+}
+
+.docs-toc a:hover {
+  color: var(--gold-ink);
+  text-decoration: underline;
+  text-underline-offset: 3px;
+}
+
+/* Prose column */
+
+.docs-prose {
+  grid-column: 1;
+  grid-row: 1;
+  min-width: 0;
+}
+
+.docs-prose .section-head {
+  margin-bottom: 1.4rem;
+}
+
+.docs-prose p + p {
+  margin-top: 0.9rem;
+}
+
+.docs-prose h3 {
+  margin-top: 2.2rem;
+}
+
+.docs-prose h3 + p,
+.docs-prose h3 + figure {
+  margin-top: 0.7rem;
+}
+
+.docs-prose code {
+  font-size: 0.88em;
+  color: var(--ink);
+  background: color-mix(in srgb, var(--gold) 16%, transparent);
+  padding: 0.08em 0.38em;
+  overflow-wrap: anywhere;
+}
+
+/* Feature ledger — what the library hands you */
+
+.docs-features {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
+  gap: 0.9rem 1.75rem;
+  margin: 0;
+  padding: 0;
+  list-style: none;
+}
+
+.docs-features li {
+  padding-left: 1rem;
+  border-left: 2px solid var(--gold-soft);
+  font-size: 0.98rem;
+  text-wrap: pretty;
+}
+
+.docs-features li strong {
+  font-family: "Cinzel", serif;
+  font-size: 0.8rem;
+  font-weight: 600;
+  letter-spacing: 0.06em;
+}
+
+/* Code plates — worked examples from the library's own source */
+
+.code-plate {
+  margin: 1.1rem 0 1.5rem;
+}
+
+.code-plate pre {
+  padding: 1.1rem 1.25rem;
+  overflow-x: auto;
+  background: var(--surface);
+  border: 1px solid var(--line);
+  border-left: 3px solid var(--gold);
+  box-shadow: var(--shadow);
+}
+
+.code-plate code {
+  font-family: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas,
+    "Liberation Mono", monospace;
+  font-size: 0.83rem;
+  line-height: 1.6;
+  color: var(--ink);
+  background: none;
+  padding: 0;
+}
+
+.code-plate figcaption {
+  margin-top: 0.55rem;
+  font-size: 0.85rem;
+  font-style: italic;
+  color: var(--ink-soft);
+  text-wrap: pretty;
+}
+
+.code-plate figcaption code {
+  font-style: normal;
+  font-size: 0.88em;
+  overflow-wrap: anywhere;
+  background: color-mix(in srgb, var(--gold) 16%, transparent);
+  padding: 0.08em 0.38em;
+}
+
+/* Honest margins: notes, caveats and provenance */
+
+.docs-note {
+  margin: 1.4rem 0;
+  padding: 0.9rem 1.2rem;
+  background: color-mix(in srgb, var(--gold) 8%, transparent);
+  border: 1px solid var(--gold-soft);
+  font-size: 0.95rem;
+  color: var(--ink-soft);
+  text-wrap: pretty;
+}
+
+.docs-note strong {
+  font-family: "Cinzel", serif;
+  font-size: 0.72rem;
+  font-weight: 600;
+  letter-spacing: 0.12em;
+  text-transform: uppercase;
+  color: var(--gold-ink);
+}
+
+/* Package ledger — what USM provides and needs */
+
+.docs-package {
+  display: grid;
+  gap: 0;
+  margin: 0;
+  border-top: 1px solid var(--line);
+}
+
+.docs-package > div {
+  display: grid;
+  grid-template-columns: minmax(7rem, 11rem) 1fr;
+  gap: 1rem;
+  padding: 0.55rem 0.25rem;
+  border-bottom: 1px solid var(--line);
+  font-size: 0.95rem;
+}
+
+.docs-package dt {
+  font-family: "Cinzel", serif;
+  font-size: 0.7rem;
+  font-weight: 600;
+  letter-spacing: 0.12em;
+  text-transform: uppercase;
+  color: var(--gold-ink);
+  padding-top: 0.2rem;
+}
+
+.docs-package dd {
+  margin: 0;
+  color: var(--ink-soft);
+}
+
+.docs-package dd code {
+  font-size: 0.88em;
+  color: var(--ink);
+  background: color-mix(in srgb, var(--gold) 16%, transparent);
+  padding: 0.05em 0.35em;
+}
+
+/* Pager — back to the almanac, on to the siblings */
+
+.docs-pager {
+  display: grid;
+  grid-template-columns: 1fr auto 1fr;
+  gap: 1rem;
+  align-items: center;
+  margin-top: clamp(2.5rem, 6vw, 4rem);
+  padding-top: 1.4rem;
+  border-top: 1px solid var(--line);
+}
+
+.docs-pager__link {
+  display: block;
+  max-width: 24rem;
+  text-decoration: none;
+}
+
+.docs-pager__link span {
+  display: block;
+  font-family: "Cinzel", serif;
+  font-size: 0.66rem;
+  font-weight: 600;
+  letter-spacing: 0.14em;
+  text-transform: uppercase;
+  color: var(--gold-ink);
+}
+
+.docs-pager__link strong {
+  font-weight: 600;
+  color: var(--ink);
+}
+
+.docs-pager__link--next {
+  grid-column: 3;
+  text-align: right;
+  justify-self: end;
+}
+
+.docs-pager__link--prev {
+  grid-column: 1;
+}
+
+.docs-pager__up {
+  grid-column: 2;
+  font-family: "Cinzel", serif;
+  font-size: 0.7rem;
+  font-weight: 600;
+  letter-spacing: 0.12em;
+  text-transform: uppercase;
+}
+
+.docs-pager__up a {
+  color: var(--ink-soft);
+  text-decoration: none;
+}
+
+.docs-pager__up a:hover {
+  color: var(--gold-ink);
+}
+
+.docs-pager__link:hover strong {
+  color: var(--gold-ink);
+  text-decoration: underline;
+  text-underline-offset: 3px;
+}
+
+/* The family strip — sibling projects one hop away */
+
+.docs-siblings {
+  margin-top: 1.6rem;
+  padding-top: 1.2rem;
+  border-top: 1px solid var(--line);
+  font-size: 0.92rem;
+  color: var(--ink-soft);
+}
+
+.docs-siblings ul {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 0.35rem 1rem;
+  margin: 0.5rem 0 0;
+  padding: 0;
+  list-style: none;
+}
+
+.docs-siblings a {
+  color: var(--ink);
+  text-decoration: none;
+  border-bottom: 1px solid color-mix(in srgb, var(--gold) 55%, transparent);
+}
+
+.docs-siblings a:hover {
+  color: var(--gold-ink);
+  border-bottom-color: var(--gold);
+}
+
 @media (max-width: 38rem) {
   .stack-branches {
     grid-template-columns: 1fr;
@@ -1182,6 +1544,36 @@ a:hover {
   .foot-brand {
     grid-column: 1 / -1;
   }
+
+  .docs-layout {
+    grid-template-columns: minmax(0, 1fr);
+  }
+
+  .docs-toc {
+    grid-column: auto;
+    grid-row: auto;
+    position: static;
+    min-width: 0;
+  }
+
+  .docs-prose {
+    grid-column: auto;
+    grid-row: auto;
+    min-width: 0;
+  }
+
+  .docs-pager {
+    grid-template-columns: 1fr;
+    justify-items: center;
+    text-align: center;
+  }
+
+  .docs-pager__link--prev,
+  .docs-pager__link--next {
+    grid-column: auto;
+    text-align: center;
+    justify-self: center;
+  }
 }
 
 @media (max-width: 38rem) {
@@ -1191,12 +1583,18 @@ a:hover {
 
   .kicker span {
     letter-spacing: 0.18em;
+    white-space: normal;
   }
 
   .release-table {
     display: block;
     overflow-x: auto;
   }
+
+  .docs-package > div {
+    grid-template-columns: 1fr;
+    gap: 0.2rem;
+  }
 }
 
 @media (prefers-reduced-motion: reduce) {

+ 3 - 7
check.py

@@ -26,14 +26,10 @@ EXCLUDE_DIRS = {"Original", "Proposed", ".git"}
 # Site-root-relative pages that are linked ahead of their existence because a
 # later task in this goal builds them. Remove entries here once the page lands
 # so the checker enforces them again.
-# TODO(A3/A4/A5): the eleven project detail pages land in waves 3-4; strip
-# these entries as each wave merges.
+# TODO(A4/A5): the six remaining detail pages land in wave 3; strip these
+# entries as each wave merges. The five Invercargill-family pages (A3) have
+# landed and are enforced.
 UPCOMING_PAGES = {
-    "developer/invercargill/",
-    "developer/invercargill-json/",
-    "developer/invercargill-sql/",
-    "developer/invercargill-sql-inversion/",
-    "developer/inversion/",
     "developer/astralis/",
     "developer/statum/",
     "developer/spry/",

+ 253 - 0
developer/invercargill-json/index.html

@@ -0,0 +1,253 @@
+<!doctype html>
+<html lang="en-nz">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>Invercargill-Json — Astrologue Developer</title>
+  <meta name="description" content="JSON support for Invercargill: documents become Enumerable collections that query and transform like any other sequence — JsonElement, JsonArray, JsonObject and JSON Lines streams. Free software, LGPL-3.0-or-later.">
+  <meta name="color-scheme" content="light dark">
+  <meta name="theme-color" content="#f2ebda" media="(prefers-color-scheme: light)">
+  <meta name="theme-color" content="#0a0a12" media="(prefers-color-scheme: dark)">
+  <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Ccircle cx='32' cy='32' r='27' fill='none' stroke='%23a8801f' stroke-width='4'/%3E%3Cpath d='M32 8l6 18 18 6-18 6-6 18-6-18-18-6 18-6z' fill='%23a8801f'/%3E%3Ccircle cx='32' cy='32' r='4' fill='%23003078'/%3E%3C/svg%3E">
+  <meta property="og:title" content="Invercargill-Json — Astrologue Developer">
+  <meta property="og:description" content="JSON support for Invercargill: documents become Enumerable collections that query and transform like any other sequence.">
+  <meta property="og:type" content="website">
+  <link rel="stylesheet" href="../../assets/css/style.css">
+</head>
+<body>
+  <a class="skip-link" href="#main">Skip to content</a>
+
+  <header class="masthead">
+    <div class="container masthead__inner">
+      <a class="brand" href="../../">
+        <img class="brand__logo brand__logo--light" src="../../assets/img/astrologue-light.png" width="392" height="89" alt="Astrologue">
+        <img class="brand__logo brand__logo--dark" src="../../assets/img/astrologue-dark.png" width="392" height="89" alt="Astrologue">
+      </a>
+      <nav class="site-nav" aria-label="Primary">
+        <ul class="site-nav__list">
+          <li><a href="../../">Home</a></li>
+          <li><a href="../../#news">News</a></li>
+          <li><a href="../../#download">Download</a></li>
+          <li><a href="../" aria-current="page">Developer</a></li>
+          <li><a class="nav-cta" href="../../#download">Get Astrologue</a></li>
+        </ul>
+      </nav>
+    </div>
+  </header>
+
+  <main id="main">
+
+    <nav class="breadcrumb" aria-label="Breadcrumb">
+      <div class="container">
+        <ol>
+          <li><a href="../../">Astrologue</a></li>
+          <li><a href="../">Developer</a></li>
+          <li aria-current="page">Invercargill-Json</li>
+        </ol>
+      </div>
+    </nav>
+
+    <section class="docs-header">
+      <div class="container">
+        <p class="kicker"><span>Foundation · Documents as sequences</span></p>
+        <h1>Invercargill-Json</h1>
+        <p class="lede">JSON support for Invercargill: a document becomes an Enumerable collection that queries and transforms like any other sequence. Json-glib does the parsing; this library gives its nodes the interface the rest of the stack speaks.</p>
+        <dl class="docs-facts">
+          <div><dt>Version</dt> <dd><code>0.1</code></dd></div>
+          <div><dt>Licence</dt> <dd>LGPL-3.0-or-later</dd></div>
+          <div><dt>Package</dt> <dd><code>usm&nbsp;install&nbsp;invercargill-json</code></dd></div>
+          <div><dt>Language</dt> <dd>Vala</dd></div>
+        </dl>
+      </div>
+    </section>
+
+    <section class="band band--plain" id="docs">
+      <div class="container docs-layout">
+
+        <nav class="docs-toc" aria-label="On this page">
+          <h2>On this page</h2>
+          <ul>
+            <li><a href="#overview">Overview</a></li>
+            <li><a href="#features">Features</a></li>
+            <li><a href="#installation">Installation</a></li>
+            <li><a href="#usage">Usage &amp; documentation</a></li>
+            <li><a href="#package">Package &amp; source</a></li>
+          </ul>
+        </nav>
+
+        <div class="docs-prose">
+
+          <header class="section-head" id="overview">
+            <p class="kicker"><span>Overview</span></p>
+            <h2>A compact companion</h2>
+          </header>
+          <p>Invercargill-Json provides JSON functionality to Invercargill — that single sentence is the library's whole README, and it is an honest measure of the project's scope. This is a compact companion library, not an exercise in surface area: five small source files that wrap json-glib's nodes in Invercargill types so a JSON document answers the same queries as a database result or a settings file.</p>
+          <p>Its worth shows up in the stack above it: Statum's signed state slots serialise through it, and the fixes it carries — integers serialising as JSON numbers rather than boxed strings, large <code>int64</code> values round-tripping without precision loss — were found in exactly that downstream path.</p>
+
+          <header class="section-head" id="features">
+            <p class="kicker"><span>Features</span></p>
+            <h2>What it hands you</h2>
+          </header>
+          <ul class="docs-features">
+            <li><strong>JsonElement.</strong> One element type for any node, parsed from a string, a file or a stream — including an async stream variant.</li>
+            <li><strong>Typed access.</strong> <code>assert_as&lt;T&gt;</code> and <code>try_get_as&lt;T&gt;</code> turn nodes into the Vala types you actually wanted.</li>
+            <li><strong>Queryable documents.</strong> Arrays and objects are Enumerable: filter, project, aggregate and order a document with the same calls as any sequence.</li>
+            <li><strong>JsonElements promotions.</strong> <code>as_strings()</code>, <code>as_integers()</code>, <code>as_doubles()</code>, <code>as_bools()</code>, <code>as_objects()</code>, <code>as_property_groups()</code> project a node sequence straight onto typed values.</li>
+            <li><strong>JSON Lines.</strong> Stream <code>.jsonl</code> in and out through <code>JsonlInputStream</code> and <code>JsonlOutputStream</code>, one element at a time.</li>
+            <li><strong>Objects as Properties.</strong> A <code>JsonObject</code> is an associative Enumerable of key-value pairs, so property mapping works on documents too.</li>
+          </ul>
+
+          <header class="section-head" id="installation">
+            <p class="kicker"><span>Installation</span></p>
+            <h2>Getting the library</h2>
+          </header>
+          <p>Install the foundation first, then this companion:</p>
+          <figure class="code-plate">
+            <pre><code>usm install invercargill
+usm install invercargill-json</code></pre>
+            <figcaption>USM resolves the <code>invercargill-1</code> and <code>json-glib</code> dependencies itself.</figcaption>
+          </figure>
+          <p>From source:</p>
+          <figure class="code-plate">
+            <pre><code>git clone &lt;the Invercargill-Json repository&gt;
+cd Invercargill-Json
+meson setup build
+ninja -C build
+ninja -C build install</code></pre>
+            <figcaption>Meson and ninja are the only build tools needed.</figcaption>
+          </figure>
+          <figure class="code-plate">
+            <pre><code>valac --pkg invercargill-json my-program.vala</code></pre>
+            <figcaption>The binding pulls in its <code>.deps</code> chain automatically.</figcaption>
+          </figure>
+
+          <header class="section-head" id="usage">
+            <p class="kicker"><span>Usage &amp; documentation</span></p>
+            <h2>Reading documents as sequences</h2>
+          </header>
+          <p>Parse, assert the shape, project to types — the library's own tests read like documentation:</p>
+          <figure class="code-plate">
+            <pre><code>using Invercargill;
+using InvercargillJson;
+
+var json_str = """[
+    "Glengarry",
+    "Gladstone",
+    "Strathern",
+    "Richmond",
+    "Georgetown"
+]""";
+
+var suburbs = new JsonElement.from_string(json_str)
+    .assert_as&lt;JsonArray&gt;()
+    .as_strings()
+    .to_array();
+// suburbs.length == 5, suburbs[0] == "Glengarry"</code></pre>
+            <figcaption>From <code>src/tests/Integration/Json.vala</code> — a document becomes a typed Vala array in four calls.</figcaption>
+          </figure>
+          <p>Objects read by key, with nullable getters that tell you what is actually there:</p>
+          <figure class="code-plate">
+            <pre><code>var obj = new JsonElement.from_string("""
+    { "name": "Aurora", "age": 27, "active": true }
+""").assert_as&lt;JsonObject&gt;();
+
+string? name    = obj.get_string("name");
+int64?  age     = obj.get_integer("age");
+bool?   active  = obj.get_bool("active");
+double? height  = obj.get_double("height");   // null — absent keys answer null</code></pre>
+            <figcaption>From <code>src/lib/Object.vala</code> — <code>get_array</code>, <code>get_object</code>, <code>try_get</code> and <code>has</code> complete the set.</figcaption>
+          </figure>
+          <p>Because documents are Enumerables, the whole modifier vocabulary applies. Select the fields you want from every record in an array of objects; filter objects on their contents; hand the result to anything that consumes a sequence.</p>
+          <p>Scalars and whole classes map onto the same representation:</p>
+          <figure class="code-plate">
+            <pre><code>// A GObject property becomes a JSON number, faithfully
+var v = Value(typeof(int64));
+v.set_int64(99);
+var j = new JsonElement.from_element(new ValueElement(v));
+assert(j.stringify() == "99");</code></pre>
+            <figcaption>From <code>src/tests/Integration/FromElement.vala</code> — the regression tests for scalar dispatch, including <code>int64</code> precision at 2⁵³&nbsp;+&nbsp;1.</figcaption>
+          </figure>
+          <p>For line-delimited streams — logs, exports, model output — the JSON Lines classes keep memory flat:</p>
+          <figure class="code-plate">
+            <pre><code>// Reading: one element at a time, or as a lazy Enumerable
+var input = new JsonlInputStream.from_file(File.new_for_path("events.jsonl"));
+JsonElement? element = input.read_next();
+
+// Writing
+var output = new JsonlOutputStream(stream);
+output.write(element);
+output.write_all(elements);</code></pre>
+            <figcaption>From <code>src/lib/Jsonl.vala</code>.</figcaption>
+          </figure>
+
+          <header class="section-head" id="package">
+            <p class="kicker"><span>Package &amp; source</span></p>
+            <h2>The ledger</h2>
+          </header>
+          <dl class="docs-package">
+            <div><dt>Version</dt><dd><code>0.1</code> (from <code>MANIFEST.usm</code>)</dd></div>
+            <div><dt>Licence</dt><dd>LGPL-3.0-or-later — free to run, study, share and improve; the LGPL keeps it free to link from any program.</dd></div>
+            <div><dt>Package</dt><dd><code>usm install invercargill-json</code></dd></div>
+            <div><dt>Provides</dt><dd><code>libinvercargill-json.so</code>, <code>invercargill-json.h</code>, <code>invercargill-json.vapi</code> + <code>.deps</code>, <code>invercargill_json-1.0.gir</code>, <code>invercargill_json-1.0.typelib</code>, <code>invercargill-json.pc</code></dd></div>
+            <div><dt>Needs at runtime</dt><dd>GLib, GObject, Gio, json-glib, and <code>libinvercargill-1.so</code> — its reason for being.</dd></div>
+            <div><dt>Source</dt><dd>The repository <code>Tilo15/Invercargill-Json</code> on the Astrologue forge (git over ssh, port 222). Free software: the licence guarantees your right to the source.</dd></div>
+          </dl>
+
+          <nav class="docs-siblings" aria-label="Neighbours in the catalogue">
+            <p>Its neighbours:</p>
+            <ul>
+              <li><a href="../invercargill/">Invercargill</a></li>
+              <li><a href="../inversion/">Inversion</a></li>
+              <li><a href="../statum/">Statum</a></li>
+            </ul>
+          </nav>
+
+          <nav class="docs-pager" aria-label="Project pages">
+            <a class="docs-pager__link docs-pager__link--prev" href="../invercargill/">
+              <span>Previous</span>
+              <strong>Invercargill</strong>
+            </a>
+            <p class="docs-pager__up"><a href="../">All eleven</a></p>
+            <a class="docs-pager__link docs-pager__link--next" href="../inversion/">
+              <span>Next in the catalogue</span>
+              <strong>Inversion</strong>
+            </a>
+          </nav>
+
+        </div>
+      </div>
+    </section>
+
+  </main>
+
+  <footer class="site-footer">
+    <div class="container">
+      <div class="footer-grid">
+        <div class="foot-brand">
+          <img class="brand__logo brand__logo--dark" src="../../assets/img/astrologue-dark.png" width="392" height="89" alt="Astrologue">
+          <p>An operating system for change — kept for self sufficiency, resilience, and software freedom, so regular people can hold the power of general purpose computers in their own hands.</p>
+        </div>
+        <nav aria-label="The four freedoms">
+          <p class="foot-title">The Four Freedoms</p>
+          <ol class="freedoms">
+            <li><strong>Run</strong> the software as you wish, for any purpose.</li>
+            <li><strong>Study</strong> the source and change it to do your computing as you wish.</li>
+            <li><strong>Share</strong> copies so you can help others.</li>
+            <li><strong>Modify</strong> and publish your versions so everyone benefits.</li>
+          </ol>
+        </nav>
+        <nav aria-label="Site">
+          <p class="foot-title">Site</p>
+          <ul class="foot-list">
+            <li><a href="../../">Home</a></li>
+            <li><a href="../../#news">News</a></li>
+            <li><a href="../../#download">Download</a></li>
+            <li><a href="../">Developer</a></li>
+          </ul>
+        </nav>
+      </div>
+      <p class="colophon">Set in Cinzel and EB Garamond (SIL Open Font License). Astrologue is free software, licensed GPL-3.0-or-later: take it, tend it, pass it on. The libraries are LGPL-3.0-or-later, so they remain free to link.</p>
+    </div>
+  </footer>
+</body>
+</html>

+ 241 - 0
developer/invercargill-sql-inversion/index.html

@@ -0,0 +1,241 @@
+<!doctype html>
+<html lang="en-nz">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>Invercargill-Sql-Inversion — Astrologue Developer</title>
+  <meta name="description" content="The Inversion integrations for Invercargill-Sql: DatabaseConfigurator, MigrationStartupService and InversionTypeProvider wire the database into the dependency-injection container. Free software, LGPL-3.0-or-later.">
+  <meta name="color-scheme" content="light dark">
+  <meta name="theme-color" content="#f2ebda" media="(prefers-color-scheme: light)">
+  <meta name="theme-color" content="#0a0a12" media="(prefers-color-scheme: dark)">
+  <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Ccircle cx='32' cy='32' r='27' fill='none' stroke='%23a8801f' stroke-width='4'/%3E%3Cpath d='M32 8l6 18 18 6-18 6-6 18-6-18-18-6 18-6z' fill='%23a8801f'/%3E%3Ccircle cx='32' cy='32' r='4' fill='%23003078'/%3E%3C/svg%3E">
+  <meta property="og:title" content="Invercargill-Sql-Inversion — Astrologue Developer">
+  <meta property="og:description" content="DatabaseConfigurator, MigrationStartupService and InversionTypeProvider: the glue that wires Invercargill-Sql into the Inversion container.">
+  <meta property="og:type" content="website">
+  <link rel="stylesheet" href="../../assets/css/style.css">
+</head>
+<body>
+  <a class="skip-link" href="#main">Skip to content</a>
+
+  <header class="masthead">
+    <div class="container masthead__inner">
+      <a class="brand" href="../../">
+        <img class="brand__logo brand__logo--light" src="../../assets/img/astrologue-light.png" width="392" height="89" alt="Astrologue">
+        <img class="brand__logo brand__logo--dark" src="../../assets/img/astrologue-dark.png" width="392" height="89" alt="Astrologue">
+      </a>
+      <nav class="site-nav" aria-label="Primary">
+        <ul class="site-nav__list">
+          <li><a href="../../">Home</a></li>
+          <li><a href="../../#news">News</a></li>
+          <li><a href="../../#download">Download</a></li>
+          <li><a href="../" aria-current="page">Developer</a></li>
+          <li><a class="nav-cta" href="../../#download">Get Astrologue</a></li>
+        </ul>
+      </nav>
+    </div>
+  </header>
+
+  <main id="main">
+
+    <nav class="breadcrumb" aria-label="Breadcrumb">
+      <div class="container">
+        <ol>
+          <li><a href="../../">Astrologue</a></li>
+          <li><a href="../">Developer</a></li>
+          <li aria-current="page">Invercargill-Sql-Inversion</li>
+        </ol>
+      </div>
+    </nav>
+
+    <section class="docs-header">
+      <div class="container">
+        <p class="kicker"><span>Data · The database, wired</span></p>
+        <h1>Invercargill-Sql-Inversion</h1>
+        <p class="lede">The Inversion integrations for Invercargill-Sql. Three classes — DatabaseConfigurator, MigrationStartupService and InversionTypeProvider — turn the database layer from something you wire by hand into something the container hands you.</p>
+        <dl class="docs-facts">
+          <div><dt>Version</dt> <dd><code>0.1</code></dd></div>
+          <div><dt>Licence</dt> <dd>LGPL-3.0-or-later</dd></div>
+          <div><dt>Package</dt> <dd><code>usm&nbsp;install&nbsp;invercargill-sql-inversion</code></dd></div>
+          <div><dt>Language</dt> <dd>Vala</dd></div>
+        </dl>
+      </div>
+    </section>
+
+    <section class="band band--plain" id="docs">
+      <div class="container docs-layout">
+
+        <nav class="docs-toc" aria-label="On this page">
+          <h2>On this page</h2>
+          <ul>
+            <li><a href="#overview">Overview</a></li>
+            <li><a href="#features">Features</a></li>
+            <li><a href="#installation">Installation</a></li>
+            <li><a href="#usage">Usage &amp; documentation</a></li>
+            <li><a href="#package">Package &amp; source</a></li>
+          </ul>
+        </nav>
+
+        <div class="docs-prose">
+
+          <header class="section-head" id="overview">
+            <p class="kicker"><span>Overview</span></p>
+            <h2>Integration glue, honestly scoped</h2>
+          </header>
+          <p>This repository ships no README — its three source files are the whole story, and this page summarises what they actually do. Invercargill-Sql-Inversion is glue in the good sense: the connective tissue that lets an application declare its database, entities, projections and migrations once, and have the <a href="../inversion/">Inversion</a> container resolve <a href="../invercargill-sql/">Invercargill-Sql</a>'s connections, dialects, runners, mappers and sessions ever after.</p>
+          <p>Without it, every application repeats the same wiring — open a connection from a connection string, create a dialect, build a migration runner, register a type registry, construct sessions. With it, that wiring is one configurator object and a handful of registrations.</p>
+
+          <header class="section-head" id="features">
+            <p class="kicker"><span>Features</span></p>
+            <h2>The three classes</h2>
+          </header>
+          <ul class="docs-features">
+            <li><strong>DatabaseConfigurator.</strong> Registers a connection from a connection string, plus the dialect factory, migration runner, type provider and ORM session — with an auto-detected lifecycle.</li>
+            <li><strong>add_entity, add_migration, add_projection.</strong> Fluent registration of mappers, migrations and projection definitions, each tagged with typed metadata.</li>
+            <li><strong>MigrationStartupService.</strong> A startup-lifecycle service that gathers every registered migration and runs the schema to latest when the container initialises.</li>
+            <li><strong>InversionTypeProvider.</strong> Answers "which mapper or projection belongs to this Vala type?" by querying registration metadata — the bridge the ORM's registry needs.</li>
+            <li><strong>Lifecycle detection.</strong> In-memory databases register as singletons, file databases as scoped — the sensible default, overridable per registration.</li>
+          </ul>
+
+          <header class="section-head" id="installation">
+            <p class="kicker"><span>Installation</span></p>
+            <h2>Getting the library</h2>
+          </header>
+          <figure class="code-plate">
+            <pre><code>usm install invercargill
+usm install invercargill-sql
+usm install inversion
+usm install invercargill-sql-inversion</code></pre>
+            <figcaption>USM resolves the dependency chain; the explicit steps are for reading order.</figcaption>
+          </figure>
+          <p>From source:</p>
+          <figure class="code-plate">
+            <pre><code>git clone &lt;the Invercargill-Sql-Inversion repository&gt;
+cd Invercargill-Sql-Inversion
+meson setup build
+ninja -C build
+ninja -C build install</code></pre>
+            <figcaption>Requires the three libraries above installed first.</figcaption>
+          </figure>
+          <figure class="code-plate">
+            <pre><code>valac --pkg invercargill-sql-inversion my-program.vala</code></pre>
+            <figcaption>The binding pulls in its <code>.deps</code> chain automatically.</figcaption>
+          </figure>
+
+          <header class="section-head" id="usage">
+            <p class="kicker"><span>Usage &amp; documentation</span></p>
+            <h2>Wiring a database in one place</h2>
+          </header>
+          <p>The configurator injects the container it lives in, so configuration reads as plain registrations:</p>
+          <figure class="code-plate">
+            <pre><code>var db = new DatabaseConfigurator();
+db.register_connection("sqlite::memory:");</code></pre>
+            <figcaption>The entry point, from <code>src/DatabaseConfigurator.vala</code>. In-memory databases default to the singleton lifecycle; file databases to scoped — pass a lifecycle to override.</figcaption>
+          </figure>
+          <p>That one call registers the connection itself, a transient <code>SqlDialect</code> from the connection string's scheme, a transient <code>MigrationRunner</code>, the <code>TypeProvider</code>, and an <code>OrmSession</code> sharing the connection's lifecycle. Then entities, projections and migrations join the container with their own fluent calls:</p>
+          <figure class="code-plate">
+            <pre><code>db.add_entity&lt;User&gt;(b =&gt; b
+    .table("users")
+    .column&lt;int64?&gt;("id", u =&gt; u.id, (u, v) =&gt; u.id = v)
+    .column&lt;string&gt;("name", u =&gt; u.name, (u, v) =&gt; u.name = v));
+
+db.add_projection&lt;UserSummary&gt;(b =&gt; { /* projection columns */ });
+
+db.add_migration&lt;V001_CreateUsers&gt;();</code></pre>
+            <figcaption>Shape from <code>src/DatabaseConfigurator.vala</code> — each registration tags the container entry with typed <code>MapperTypeInfo</code> metadata.</figcaption>
+          </figure>
+          <p>To have the schema ready before the application serves anything, one more line:</p>
+          <figure class="code-plate">
+            <pre><code>db.migrate_on_startup();
+container.initialise();</code></pre>
+            <figcaption><code>migrate_on_startup()</code> registers the <code>MigrationStartupService</code> on the STARTUP lifecycle; when the container initialises, it creates a scope, resolves every registered <code>Migration</code>, hands them to the runner, and migrates to latest.</figcaption>
+          </figure>
+          <p>Downstream, the ORM asks for mappers through the type provider rather than a hand-built registry:</p>
+          <figure class="code-plate">
+            <pre><code>public class InversionTypeProvider : Object, TypeProvider {
+
+    private Scope scope = inject&lt;Scope&gt;();
+
+    public EntityMapper? get_mapper_for_type(GLib.Type type) throws Error {
+        var reg = scope.get_registrations(typeof(EntityMapper))
+            .where(r =&gt; r.get_metadata&lt;MapperTypeInfo&gt;()
+                        .any(i =&gt; i.mapper_type == type))
+            .first_or_default();
+        return reg == null ? null
+            : (EntityMapper)scope.resolve_registration(reg);
+    }
+    // get_projection_for_type, has_mapper_for_type likewise
+}</code></pre>
+            <figcaption>Condensed from <code>src/InversionTypeProvider.vala</code> — the metadata written at registration time is what answers the type question here.</figcaption>
+          </figure>
+
+          <header class="section-head" id="package">
+            <p class="kicker"><span>Package &amp; source</span></p>
+            <h2>The ledger</h2>
+          </header>
+          <dl class="docs-package">
+            <div><dt>Version</dt><dd><code>0.1</code> (from <code>MANIFEST.usm</code>)</dd></div>
+            <div><dt>Licence</dt><dd>LGPL-3.0-or-later — free to run, study, share and improve; the LGPL keeps it free to link from any program.</dd></div>
+            <div><dt>Package</dt><dd><code>usm install invercargill-sql-inversion</code></dd></div>
+            <div><dt>Provides</dt><dd><code>libinvercargill-sql-inversion.so</code>, <code>invercargill-sql-inversion.h</code>, <code>invercargill-sql-inversion.vapi</code> + <code>.deps</code>, <code>invercargill_sql_inversion-0.1.gir</code>, <code>invercargill_sql_inversion-0.1.typelib</code>, <code>invercargill-sql-inversion.pc</code></dd></div>
+            <div><dt>Needs at runtime</dt><dd>GLib, GObject, <code>libinvercargill-1.so</code>, <code>libinvercargill-sql.so</code> and <code>libinversion-0.1.so</code>.</dd></div>
+            <div><dt>Source</dt><dd>The repository <code>Tilo15/Invercargill-Sql-Inversion</code> on the Astrologue forge (git over ssh, port 222). Free software: the licence guarantees your right to the source.</dd></div>
+          </dl>
+
+          <nav class="docs-siblings" aria-label="Neighbours in the catalogue">
+            <p>Its neighbours:</p>
+            <ul>
+              <li><a href="../invercargill-sql/">Invercargill-Sql</a></li>
+              <li><a href="../inversion/">Inversion</a></li>
+              <li><a href="../spry/">Spry</a></li>
+            </ul>
+          </nav>
+
+          <nav class="docs-pager" aria-label="Project pages">
+            <a class="docs-pager__link docs-pager__link--prev" href="../invercargill-sql/">
+              <span>Previous</span>
+              <strong>Invercargill-Sql</strong>
+            </a>
+            <p class="docs-pager__up"><a href="../">All eleven</a></p>
+            <a class="docs-pager__link docs-pager__link--next" href="../astralis/">
+              <span>Next in the catalogue</span>
+              <strong>Astralis</strong>
+            </a>
+          </nav>
+
+        </div>
+      </div>
+    </section>
+
+  </main>
+
+  <footer class="site-footer">
+    <div class="container">
+      <div class="footer-grid">
+        <div class="foot-brand">
+          <img class="brand__logo brand__logo--dark" src="../../assets/img/astrologue-dark.png" width="392" height="89" alt="Astrologue">
+          <p>An operating system for change — kept for self sufficiency, resilience, and software freedom, so regular people can hold the power of general purpose computers in their own hands.</p>
+        </div>
+        <nav aria-label="The four freedoms">
+          <p class="foot-title">The Four Freedoms</p>
+          <ol class="freedoms">
+            <li><strong>Run</strong> the software as you wish, for any purpose.</li>
+            <li><strong>Study</strong> the source and change it to do your computing as you wish.</li>
+            <li><strong>Share</strong> copies so you can help others.</li>
+            <li><strong>Modify</strong> and publish your versions so everyone benefits.</li>
+          </ol>
+        </nav>
+        <nav aria-label="Site">
+          <p class="foot-title">Site</p>
+          <ul class="foot-list">
+            <li><a href="../../">Home</a></li>
+            <li><a href="../../#news">News</a></li>
+            <li><a href="../../#download">Download</a></li>
+            <li><a href="../">Developer</a></li>
+          </ul>
+        </nav>
+      </div>
+      <p class="colophon">Set in Cinzel and EB Garamond (SIL Open Font License). Astrologue is free software, licensed GPL-3.0-or-later: take it, tend it, pass it on. The libraries are LGPL-3.0-or-later, so they remain free to link.</p>
+    </div>
+  </footer>
+</body>
+</html>

+ 369 - 0
developer/invercargill-sql/index.html

@@ -0,0 +1,369 @@
+<!doctype html>
+<html lang="en-nz">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>Invercargill-Sql — Astrologue Developer</title>
+  <meta name="description" content="A SQL abstraction built on Invercargill data structures: provider-neutral connections, lazy Enumerable rows, fluent parameter binding, transactions, migrations and an ORM with entity mappers and projections. Free software, LGPL-3.0-or-later.">
+  <meta name="color-scheme" content="light dark">
+  <meta name="theme-color" content="#f2ebda" media="(prefers-color-scheme: light)">
+  <meta name="theme-color" content="#0a0a12" media="(prefers-color-scheme: dark)">
+  <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Ccircle cx='32' cy='32' r='27' fill='none' stroke='%23a8801f' stroke-width='4'/%3E%3Cpath d='M32 8l6 18 18 6-18 6-6 18-6-18-18-6 18-6z' fill='%23a8801f'/%3E%3Ccircle cx='32' cy='32' r='4' fill='%23003078'/%3E%3C/svg%3E">
+  <meta property="og:title" content="Invercargill-Sql — Astrologue Developer">
+  <meta property="og:description" content="A generic SQL abstraction on Invercargill data structures: lazy Enumerable rows over a provider-neutral API, with migrations and an ORM.">
+  <meta property="og:type" content="website">
+  <link rel="stylesheet" href="../../assets/css/style.css">
+</head>
+<body>
+  <a class="skip-link" href="#main">Skip to content</a>
+
+  <header class="masthead">
+    <div class="container masthead__inner">
+      <a class="brand" href="../../">
+        <img class="brand__logo brand__logo--light" src="../../assets/img/astrologue-light.png" width="392" height="89" alt="Astrologue">
+        <img class="brand__logo brand__logo--dark" src="../../assets/img/astrologue-dark.png" width="392" height="89" alt="Astrologue">
+      </a>
+      <nav class="site-nav" aria-label="Primary">
+        <ul class="site-nav__list">
+          <li><a href="../../">Home</a></li>
+          <li><a href="../../#news">News</a></li>
+          <li><a href="../../#download">Download</a></li>
+          <li><a href="../" aria-current="page">Developer</a></li>
+          <li><a class="nav-cta" href="../../#download">Get Astrologue</a></li>
+        </ul>
+      </nav>
+    </div>
+  </header>
+
+  <main id="main">
+
+    <nav class="breadcrumb" aria-label="Breadcrumb">
+      <div class="container">
+        <ol>
+          <li><a href="../../">Astrologue</a></li>
+          <li><a href="../">Developer</a></li>
+          <li aria-current="page">Invercargill-Sql</li>
+        </ol>
+      </div>
+    </nav>
+
+    <section class="docs-header">
+      <div class="container">
+        <p class="kicker"><span>Data · Rows, schemas and migrations</span></p>
+        <h1>Invercargill-Sql</h1>
+        <p class="lede">A generic SQL abstraction built on Invercargill data structures. Connections are provider-neutral, query results return as lazy Enumerable rows, and above that sits a migration system and an ORM with entity mappers and projections. SQLite is the engine beneath today.</p>
+        <dl class="docs-facts">
+          <div><dt>Version</dt> <dd><code>0.1</code></dd></div>
+          <div><dt>Licence</dt> <dd>LGPL-3.0-or-later</dd></div>
+          <div><dt>Package</dt> <dd><code>usm&nbsp;install&nbsp;invercargill-sql</code></dd></div>
+          <div><dt>Language</dt> <dd>Vala</dd></div>
+        </dl>
+      </div>
+    </section>
+
+    <section class="band band--plain" id="docs">
+      <div class="container docs-layout">
+
+        <nav class="docs-toc" aria-label="On this page">
+          <h2>On this page</h2>
+          <ul>
+            <li><a href="#overview">Overview</a></li>
+            <li><a href="#features">Features</a></li>
+            <li><a href="#installation">Installation</a></li>
+            <li><a href="#usage">Usage &amp; documentation</a></li>
+            <li><a href="#package">Package &amp; source</a></li>
+          </ul>
+        </nav>
+
+        <div class="docs-prose">
+
+          <header class="section-head" id="overview">
+            <p class="kicker"><span>Overview</span></p>
+            <h2>Why it exists</h2>
+          </header>
+          <p>Databases in Vala usually mean provider-specific APIs and eager row fetching. Invercargill-Sql takes the other road: a small public surface — <code>ConnectionFactory</code>, <code>Connection</code>, <code>Command</code>, <code>Transaction</code> — under which the concrete engine is an internal detail. Query results come back as <code>Enumerable&lt;Properties&gt;</code>, so a result set is not a materialised list you then re-shape but a lazy sequence you query with the same operations as everything else in the stack.</p>
+          <p>On that ground stands the rest of the data story: a fluent migration system that builds and versions schemas, an ORM that maps GObject classes onto tables with schema introspection, and projections that shape joins and aggregates into purpose-built read models. Spry's user and permission tables are defined here, as migrations.</p>
+
+          <header class="section-head" id="features">
+            <p class="kicker"><span>Features</span></p>
+            <h2>What it hands you</h2>
+          </header>
+          <ul class="docs-features">
+            <li><strong>Generic engine interface.</strong> Abstract away specific SQL server implementations; SQLite ships as the first-class engine.</li>
+            <li><strong>Lazy Enumerable rows.</strong> Query results return as <code>Enumerable&lt;Properties&gt;</code> — filter, project and cache them with Invercargill itself.</li>
+            <li><strong>Connection strings.</strong> URI-style strings such as <code>sqlite::memory:</code> or <code>sqlite:///db.sqlite?mode=ro</code>, parsed into scheme, database and options.</li>
+            <li><strong>Parameterized queries.</strong> A fluent API for type-safe parameter binding.</li>
+            <li><strong>Transactions.</strong> Full commit/rollback management.</li>
+            <li><strong>Async throughout.</strong> Every operation has an async variant for non-blocking I/O.</li>
+            <li><strong>Migrations.</strong> A fluent builder for tables, columns, indexes and foreign keys, with dependency-aware ordering and <code>up</code>/<code>down</code> reversibility.</li>
+            <li><strong>ORM.</strong> Entity mappers, sessions, typed queries and projections — including join and aggregate read models.</li>
+          </ul>
+
+          <header class="section-head" id="installation">
+            <p class="kicker"><span>Installation</span></p>
+            <h2>Getting the library</h2>
+          </header>
+          <figure class="code-plate">
+            <pre><code>usm install invercargill
+usm install invercargill-sql</code></pre>
+            <figcaption>USM resolves SQLite and the other build dependencies itself.</figcaption>
+          </figure>
+          <p>From source:</p>
+          <figure class="code-plate">
+            <pre><code>git clone &lt;the Invercargill-Sql repository&gt;
+cd Invercargill-Sql
+meson setup build
+ninja -C build
+ninja -C build install</code></pre>
+            <figcaption>The test suite runs with <code>meson test -C build</code>.</figcaption>
+          </figure>
+          <figure class="code-plate">
+            <pre><code>valac --pkg invercargill-sql my-program.vala</code></pre>
+            <figcaption>The binding pulls in its <code>.deps</code> chain automatically.</figcaption>
+          </figure>
+
+          <header class="section-head" id="usage">
+            <p class="kicker"><span>Usage &amp; documentation</span></p>
+            <h2>Connecting and querying</h2>
+          </header>
+          <p>Use <code>ConnectionFactory</code> to create connections — no database-specific types in sight:</p>
+          <figure class="code-plate">
+            <pre><code>using InvercargillSql;
+
+void main() throws SqlError {
+    // Create and open a connection using a connection string
+    var conn = ConnectionFactory.create_and_open("sqlite:///mydb.db");
+
+    // Or create and open separately
+    var conn2 = ConnectionFactory.create("sqlite:///another.db");
+    conn2.open();
+
+    // Execute commands...
+
+    conn.close();
+    conn2.close();
+}</code></pre>
+            <figcaption>From the README. Connection strings also cover <code>sqlite://./relative/path.db</code>, <code>sqlite::memory:</code> and read-only or read-write modes via <code>?mode=</code>.</figcaption>
+          </figure>
+          <p>DDL and inserts go through fluent commands with bound parameters:</p>
+          <figure class="code-plate">
+            <pre><code>// Create a table
+conn.create_command("""
+    CREATE TABLE IF NOT EXISTS users (
+        id INTEGER PRIMARY KEY,
+        name TEXT NOT NULL,
+        email TEXT
+    )
+""").execute_non_query();
+
+// Insert data with parameters
+conn.create_command("INSERT INTO users (name, email) VALUES (:name, :email)")
+    .with_parameter("name", "Alice")
+    .with_parameter("email", "alice@example.com")
+    .execute_non_query();</code></pre>
+            <figcaption>From the README.</figcaption>
+          </figure>
+          <p>Query results are lazy — enumerate them, read columns by name, or keep querying:</p>
+          <figure class="code-plate">
+            <pre><code>var results = conn.create_command("SELECT * FROM users")
+    .execute_query();
+
+foreach (var row in results) {
+    string? name  = row.get("name")?.as_string_or_null();
+    string? email = row.get("email")?.as_string_or_null();
+    int64?  id    = row.get("id")?.as&lt;int64?&gt;();
+
+    print(@"User: $name ($email)\n");
+}
+
+// Results are Enumerable&lt;Properties&gt; — the whole toolkit applies
+var activeUsers = results.where(row =&gt; row.get("active")?.as&lt;bool&gt;() ?? false);
+var names       = results.select(row =&gt; row.get("name")?.as_string_or_null() ?? "unknown");
+var cached      = results.cache();</code></pre>
+            <figcaption>From the README — the second half shows Invercargill's modifiers applied directly to a result set.</figcaption>
+          </figure>
+          <p>Transactions commit or roll back as a unit, and every operation has an async twin:</p>
+          <figure class="code-plate">
+            <pre><code>var txn = conn.begin_transaction();
+
+try {
+    conn.create_command("INSERT INTO users (name) VALUES ('Charlie')")
+        .execute_non_query();
+    conn.create_command("INSERT INTO users (name) VALUES ('Diana')")
+        .execute_non_query();
+
+    txn.commit();
+} catch (SqlError e) {
+    txn.rollback();
+    warning("Transaction failed: %s", e.message);
+}</code></pre>
+            <figcaption>From the README. Async: <code>conn.open_async()</code>, <code>conn.execute_query_async(...)</code>, <code>conn.close_async()</code>.</figcaption>
+          </figure>
+
+          <h3>Migrations — schemas as code</h3>
+          <p>Subclass <code>Migration</code>, declare a namespace, serial and name, and build your schema with the fluent builder:</p>
+          <figure class="code-plate">
+            <pre><code>using InvercargillSql;
+using InvercargillSql.Migrations;
+
+public class V001_CreateUsers : Migration {
+    public override string migration_namespace { get { return "app"; } }
+    public override uint64 serial { get { return 1; } }
+    public override string name { get { return "CreateUsers"; } }
+
+    public override void up(MigrationBuilder b) throws SqlError {
+        b.create_table("users", t =&gt; {
+            t.column&lt;int64?&gt;("id")
+                .primary_key()
+                .auto_increment();
+            t.column&lt;string&gt;("name")
+                .not_null();
+            t.column&lt;string&gt;("email")
+                .unique();
+            t.column&lt;int64?&gt;("age");
+            t.column&lt;bool?&gt;("is_active");
+
+            // Create index on email for fast lookups
+            t.index("idx_users_email").on_column("email");
+        });
+    }
+
+    public override void down(MigrationBuilder b) throws SqlError {
+        b.drop_table("users");
+    }
+}</code></pre>
+            <figcaption>Verbatim from <code>examples/migrations/v001-create-users.vala</code>.</figcaption>
+          </figure>
+          <figure class="code-plate">
+            <pre><code>var runner = new MigrationRunner(conn, dialect);
+runner.register_migration(new V001_CreateUsers());
+runner.register_migration(new V002_CreateProducts());
+runner.register_migration(new V003_CreateOrders());
+
+runner.migrate_to_latest();
+var applied = runner.get_applied_migrations();</code></pre>
+            <figcaption>From <code>examples/demo.vala</code> — the runner orders migrations and remembers what is applied.</figcaption>
+          </figure>
+
+          <h3>The ORM — entities, sessions, projections</h3>
+          <p>Map a GObject class onto a table once; sessions do the rest:</p>
+          <figure class="code-plate">
+            <pre><code>public class User : Object {
+    public int64 id { get; set; }
+    public string name { get; set; }
+    public string email { get; set; }
+    public int64 age { get; set; }
+    public bool is_active { get; set; }
+
+    public static void configure_mapper(EntityMapperBuilder&lt;User&gt; b) {
+        b.table("users")
+            .column&lt;int64?&gt;("id", u =&gt; u.id, (u, v) =&gt; u.id = v)
+            .column&lt;string&gt;("name", u =&gt; u.name, (u, v) =&gt; u.name = v)
+            .column&lt;string&gt;("email", u =&gt; u.email, (u, v) =&gt; u.email = v)
+            .column&lt;int64?&gt;("age", u =&gt; u.age, (u, v) =&gt; u.age = v)
+            .column&lt;bool?&gt;("is_active", u =&gt; u.is_active, (u, v) =&gt; u.is_active = v);
+    }
+}</code></pre>
+            <figcaption>From <code>examples/entities/user.vala</code> — the mapper introspects the live schema at registration.</figcaption>
+          </figure>
+          <figure class="code-plate">
+            <pre><code>var registry = new TypeRegistry();
+registry.register_with_schema&lt;User&gt;("users", conn, dialect, User.configure_mapper);
+var session = new OrmSession(conn, registry, dialect);
+
+// Insert
+var alice = new User();
+alice.name = "Alice";
+alice.email = "alice@example.com";
+session.insert(alice);
+
+// Query with typed predicates and ordering
+var users_over_25 = session.query&lt;User&gt;()
+    .where(expr("age &gt; $0", new NativeElement&lt;int?&gt;(25)))
+    .materialise();
+
+var active_users = session.query&lt;User&gt;()
+    .where(expr("is_active == $0", new NativeElement&lt;bool?&gt;(true)))
+    .order_by(expr("age"))
+    .materialise();
+
+// Update and delete
+alice.age = 31;
+session.update(alice);
+session.delete(alice);</code></pre>
+            <figcaption>From <code>examples/demo.vala</code> — <code>expr()</code> builds parameterised predicates; pagination is <code>.limit(2).offset(1)</code>.</figcaption>
+          </figure>
+          <p>Projections complete the ORM: purpose-built read models — simple, joined and aggregate — registered per type and queried through the same sessions, as <code>UserSummary</code>, <code>OrderDetail</code> and <code>SalesReport</code> in the demo show.</p>
+          <p>The repository's <code>examples/demo.vala</code> runs the whole story — migrations, registration, inserts, queries, updates, deletes, projections — against an in-memory database; its <code>docs/ARCHITECTURE.md</code> and <code>plans/</code> directory record how the layered design was reached.</p>
+
+          <header class="section-head" id="package">
+            <p class="kicker"><span>Package &amp; source</span></p>
+            <h2>The ledger</h2>
+          </header>
+          <dl class="docs-package">
+            <div><dt>Version</dt><dd><code>0.1</code> (from <code>MANIFEST.usm</code>)</dd></div>
+            <div><dt>Licence</dt><dd>LGPL-3.0-or-later — free to run, study, share and improve; the LGPL keeps it free to link from any program.</dd></div>
+            <div><dt>Package</dt><dd><code>usm install invercargill-sql</code></dd></div>
+            <div><dt>Provides</dt><dd><code>libinvercargill-sql.so</code>, <code>invercargill-sql.h</code>, <code>invercargill-sql.vapi</code> + <code>.deps</code>, <code>invercargill_sql-1.0.gir</code>, <code>invercargill_sql-1.0.typelib</code>, <code>invercargill-sql.pc</code></dd></div>
+            <div><dt>Needs at runtime</dt><dd>GLib, GObject, Gio, <code>libinvercargill-1.so</code> and <code>libsqlite3.so.0</code>.</dd></div>
+            <div><dt>Source</dt><dd>The repository <code>Tilo15/Invercargill-Sql</code> on the Astrologue forge (git over ssh, port 222). Free software: the licence guarantees your right to the source.</dd></div>
+          </dl>
+
+          <nav class="docs-siblings" aria-label="Neighbours in the catalogue">
+            <p>Its neighbours:</p>
+            <ul>
+              <li><a href="../inversion/">Inversion</a></li>
+              <li><a href="../invercargill-sql-inversion/">Invercargill-Sql-Inversion</a></li>
+              <li><a href="../spry/">Spry</a></li>
+            </ul>
+          </nav>
+
+          <nav class="docs-pager" aria-label="Project pages">
+            <a class="docs-pager__link docs-pager__link--prev" href="../inversion/">
+              <span>Previous</span>
+              <strong>Inversion</strong>
+            </a>
+            <p class="docs-pager__up"><a href="../">All eleven</a></p>
+            <a class="docs-pager__link docs-pager__link--next" href="../invercargill-sql-inversion/">
+              <span>Next in the catalogue</span>
+              <strong>Invercargill-Sql-Inversion</strong>
+            </a>
+          </nav>
+
+        </div>
+      </div>
+    </section>
+
+  </main>
+
+  <footer class="site-footer">
+    <div class="container">
+      <div class="footer-grid">
+        <div class="foot-brand">
+          <img class="brand__logo brand__logo--dark" src="../../assets/img/astrologue-dark.png" width="392" height="89" alt="Astrologue">
+          <p>An operating system for change — kept for self sufficiency, resilience, and software freedom, so regular people can hold the power of general purpose computers in their own hands.</p>
+        </div>
+        <nav aria-label="The four freedoms">
+          <p class="foot-title">The Four Freedoms</p>
+          <ol class="freedoms">
+            <li><strong>Run</strong> the software as you wish, for any purpose.</li>
+            <li><strong>Study</strong> the source and change it to do your computing as you wish.</li>
+            <li><strong>Share</strong> copies so you can help others.</li>
+            <li><strong>Modify</strong> and publish your versions so everyone benefits.</li>
+          </ol>
+        </nav>
+        <nav aria-label="Site">
+          <p class="foot-title">Site</p>
+          <ul class="foot-list">
+            <li><a href="../../">Home</a></li>
+            <li><a href="../../#news">News</a></li>
+            <li><a href="../../#download">Download</a></li>
+            <li><a href="../">Developer</a></li>
+          </ul>
+        </nav>
+      </div>
+      <p class="colophon">Set in Cinzel and EB Garamond (SIL Open Font License). Astrologue is free software, licensed GPL-3.0-or-later: take it, tend it, pass it on. The libraries are LGPL-3.0-or-later, so they remain free to link.</p>
+    </div>
+  </footer>
+</body>
+</html>

+ 253 - 0
developer/invercargill/index.html

@@ -0,0 +1,253 @@
+<!doctype html>
+<html lang="en-nz">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>Invercargill — Astrologue Developer</title>
+  <meta name="description" content="Invercargill is the Enumerable library for Vala — LINQ-style querying plus the Vector, Dictionary, Series and buffer data structures the whole Astrologue stack is written against. Free software, LGPL-3.0-or-later.">
+  <meta name="color-scheme" content="light dark">
+  <meta name="theme-color" content="#f2ebda" media="(prefers-color-scheme: light)">
+  <meta name="theme-color" content="#0a0a12" media="(prefers-color-scheme: dark)">
+  <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Ccircle cx='32' cy='32' r='27' fill='none' stroke='%23a8801f' stroke-width='4'/%3E%3Cpath d='M32 8l6 18 18 6-18 6-6 18-6-18-18-6 18-6z' fill='%23a8801f'/%3E%3Ccircle cx='32' cy='32' r='4' fill='%23003078'/%3E%3C/svg%3E">
+  <meta property="og:title" content="Invercargill — Astrologue Developer">
+  <meta property="og:description" content="The Enumerable library for Vala: LINQ-style querying plus Vector, Dictionary, Series, buffers, sets and heaps — the foundation the Astrologue stack is written against.">
+  <meta property="og:type" content="website">
+  <link rel="stylesheet" href="../../assets/css/style.css">
+</head>
+<body>
+  <a class="skip-link" href="#main">Skip to content</a>
+
+  <header class="masthead">
+    <div class="container masthead__inner">
+      <a class="brand" href="../../">
+        <img class="brand__logo brand__logo--light" src="../../assets/img/astrologue-light.png" width="392" height="89" alt="Astrologue">
+        <img class="brand__logo brand__logo--dark" src="../../assets/img/astrologue-dark.png" width="392" height="89" alt="Astrologue">
+      </a>
+      <nav class="site-nav" aria-label="Primary">
+        <ul class="site-nav__list">
+          <li><a href="../../">Home</a></li>
+          <li><a href="../../#news">News</a></li>
+          <li><a href="../../#download">Download</a></li>
+          <li><a href="../" aria-current="page">Developer</a></li>
+          <li><a class="nav-cta" href="../../#download">Get Astrologue</a></li>
+        </ul>
+      </nav>
+    </div>
+  </header>
+
+  <main id="main">
+
+    <nav class="breadcrumb" aria-label="Breadcrumb">
+      <div class="container">
+        <ol>
+          <li><a href="../../">Astrologue</a></li>
+          <li><a href="../">Developer</a></li>
+          <li aria-current="page">Invercargill</li>
+        </ol>
+      </div>
+    </nav>
+
+    <section class="docs-header">
+      <div class="container">
+        <p class="kicker"><span>Foundation · The collections beneath everything</span></p>
+        <h1>Invercargill</h1>
+        <p class="lede">The Enumerable library for Vala — LINQ-style querying plus the data structures the whole Astrologue stack is written against. Every row a database returns, every setting a server reads, every key a cryptographer signs passes through this library.</p>
+        <dl class="docs-facts">
+          <div><dt>Version</dt> <dd><code>1.0.0</code></dd></div>
+          <div><dt>Licence</dt> <dd>LGPL-3.0-or-later</dd></div>
+          <div><dt>Package</dt> <dd><code>usm&nbsp;install&nbsp;invercargill</code></dd></div>
+          <div><dt>Language</dt> <dd>Vala</dd></div>
+        </dl>
+      </div>
+    </section>
+
+    <section class="band band--plain" id="docs">
+      <div class="container docs-layout">
+
+        <nav class="docs-toc" aria-label="On this page">
+          <h2>On this page</h2>
+          <ul>
+            <li><a href="#overview">Overview</a></li>
+            <li><a href="#features">Features</a></li>
+            <li><a href="#installation">Installation</a></li>
+            <li><a href="#usage">Usage &amp; documentation</a></li>
+            <li><a href="#package">Package &amp; source</a></li>
+          </ul>
+        </nav>
+
+        <div class="docs-prose">
+
+          <header class="section-head" id="overview">
+            <p class="kicker"><span>Overview</span></p>
+            <h2>Why it exists</h2>
+          </header>
+          <p>Invercargill is a comprehensive data-manipulation library for the Vala programming language. Its central <code>Enumerable&lt;T&gt;</code> type makes working with data sets significantly more intuitive and less cognitively taxing, with a rich set of functional-programming tools for transformation, querying and manipulation.</p>
+          <p>It exists because its author — a C# developer by trade — kept missing the operations the .NET <code>IEnumerable</code> interface provides. Unwilling to settle for LibGee, he rolled a library of his own and named it after New Zealand's southernmost city. It takes heavy inspiration from C#'s interface, adapted where Vala's nuances call for it, and adds unique functions and structures of its own.</p>
+          <p>Today it is the foundation of the Astrologue stack: the query results of <a href="../invercargill-sql/">Invercargill-Sql</a> arrive as lazy <code>Enumerable</code> rows, <a href="../inversion/">Inversion</a> resolves its registrations through it, and every library above leans on its data structures instead of <code>GLib.ArrayList</code> or LibGee.</p>
+
+          <header class="section-head" id="features">
+            <p class="kicker"><span>Features</span></p>
+            <h2>What it hands you</h2>
+          </header>
+          <ul class="docs-features">
+            <li><strong>Enumerable everywhere.</strong> One interface for iteration, transformation, filtering, aggregation, set operations, ordering, partitioning and grouping — chained fluently.</li>
+            <li><strong>Data structures.</strong> Vector, Series, SortedSeries, Dictionary, HashSet, Buffer, ByteBuffer, RingBuffer, PriorityQueue, Fifo and Lifo, each with a job.</li>
+            <li><strong>Lazy evaluation.</strong> Sequences are computed as they are walked, with caching mechanisms for when a second pass should not recompute.</li>
+            <li><strong>Parallel processing.</strong> Built-in parallel operations to leverage multi-core processors.</li>
+            <li><strong>Numbers as sequences.</strong> Promotions turn an <code>Enumerable&lt;int&gt;</code> into a numeric view with <code>sum()</code>, <code>average()</code> and friends.</li>
+            <li><strong>Attempts.</strong> Safe operation handling with error recovery, so a fallible step can be retried or abandoned without ceremony.</li>
+          </ul>
+
+          <header class="section-head" id="installation">
+            <p class="kicker"><span>Installation</span></p>
+            <h2>Getting the library</h2>
+          </header>
+          <p>With USM on your machine, one command acquires, builds and installs the library together with its C header, Vala binding and introspection:</p>
+          <figure class="code-plate">
+            <pre><code>usm install invercargill</code></pre>
+            <figcaption>Installs from the Astrologue package repository.</figcaption>
+          </figure>
+          <p>From source, the library follows the stack's one-build discipline:</p>
+          <figure class="code-plate">
+            <pre><code>git clone &lt;the Invercargill repository&gt;
+cd Invercargill
+meson setup src build
+ninja -C build
+ninja -C build install</code></pre>
+            <figcaption>The build lives in <code>src/</code>; the README and the package scripts agree on it.</figcaption>
+          </figure>
+          <p>Then compile against it with Vala's usual package mechanism:</p>
+          <figure class="code-plate">
+            <pre><code>valac --pkg invercargill-1 my-program.vala</code></pre>
+            <figcaption>The <code>-1</code> suffix keeps the ABI generation honest.</figcaption>
+          </figure>
+
+          <header class="section-head" id="usage">
+            <p class="kicker"><span>Usage &amp; documentation</span></p>
+            <h2>Working with sequences</h2>
+          </header>
+          <p>Wrap anything enumerable — an array, a generic array, a stream — and work with it through the fluent API. These idioms are lifted from the library's own integration tests:</p>
+          <figure class="code-plate">
+            <pre><code>using Invercargill;
+
+var items = Wrap.array(new int[] { 1, 2, 3, 4, 5 });
+var result = items.aggregate&lt;int&gt;(0, (sum, item) =&gt; sum + item);
+// result == 15
+
+var words = Wrap.array(new string[] { "Hello", " ", "World", "!" });
+var greeting = words.aggregate&lt;string&gt;("", (concat, item) =&gt; concat + item);
+// greeting == "Hello World!"</code></pre>
+            <figcaption>From <code>src/tests/Integration/EnumerableMethods.vala</code> — <code>aggregate</code> folds a sequence down to one value.</figcaption>
+          </figure>
+          <p>Predicates come in matched pairs, and empty sequences answer honestly:</p>
+          <figure class="code-plate">
+            <pre><code>var evens = Wrap.array(new int[] { 2, 4, 6, 8 });
+evens.all(i =&gt; i % 2 == 0);   // true
+evens.any(i =&gt; i == 5);       // false
+evens.no(i =&gt; i % 2 == 1);    // true — nothing matches
+
+var empty = Wrap.array(new int[] {});
+empty.any();                  // false
+empty.no();                   // true</code></pre>
+            <figcaption><code>all</code>, <code>any</code> and <code>no</code> — the last is the existential negative LibGee never gave Vala.</figcaption>
+          </figure>
+          <p>Ordering and projection compose like their C# cousins:</p>
+          <figure class="code-plate">
+            <pre><code>var items = ClassToOrder.data();
+var result = items.order_by&lt;int&gt;(i =&gt; i.id);
+var ids     = result.select&lt;int&gt;(i =&gt; i.id);
+
+var newest_first = items.order_by_descending&lt;string&gt;(i =&gt; i.name);</code></pre>
+            <figcaption>From <code>src/tests/Integration/OrderBy.vala</code>.</figcaption>
+          </figure>
+          <p>The modifier vocabulary goes much further — <code>where()</code>, <code>from()</code>, <code>until()</code> for filtering; <code>select()</code>, <code>select_many()</code>, <code>cast()</code>, <code>of_type()</code> for transformation; <code>combine()</code>, <code>common()</code>, <code>exclude()</code>, <code>distinct()</code> for set operations; <code>take()</code>, <code>skip()</code>, <code>chunk()</code>, <code>window()</code> for partitioning; and <code>zip()</code>, <code>concat()</code>, <code>interleave()</code>, <code>cycle()</code> for combination.</p>
+
+          <h3>The data structures</h3>
+          <p>Alongside the query layer sit the collections the stack is actually written with — used in place of <code>GLib.ArrayList</code>, <code>GLib.List</code> and <code>GLib.HashTable</code>:</p>
+          <figure class="code-plate">
+            <pre><code>using Invercargill.DataStructures;
+
+var data = new Vector&lt;int&gt;();
+data.add(5);
+data.add(15);
+data.add(10);
+
+var numbers = data.assert_promotion&lt;SignedNativeIntegers&gt;();
+var mean = numbers.average();      // 10
+
+var dict = new Dictionary&lt;int, string&gt;();
+dict[1999] = "Billy";
+dict[1962] = "John";
+string? billy = dict.get_or_default(1999);</code></pre>
+            <figcaption>From <code>src/tests/Integration/Numbers.vala</code> and <code>Dictionary.vala</code>. A promotion turns a plain sequence into a numeric view.</figcaption>
+          </figure>
+          <p>The full set: <strong>Vector</strong> (dynamic array), <strong>Series</strong> (linked list, efficient at both ends), <strong>SortedSeries</strong> (red-black tree, kept in order), <strong>Dictionary</strong> (key-value store), <strong>HashSet</strong> (hash table with tombstone deletion), <strong>Buffer</strong> and <strong>ByteBuffer</strong>, <strong>ImmutableBuffer</strong>, <strong>RingBuffer</strong> (circular, iterable forever), <strong>PriorityQueue</strong> (min-heap, thread-safe push/pop), and <strong>Fifo</strong>/<strong>Lifo</strong> (thread-safe queue and stack with blocking operations and unblocking support).</p>
+          <p>The library also carries an expressions subsystem, property mapping, debugging utilities and the promotion system shown above. Its test suite — integration and speed — runs through <code>meson test</code> and through USM's <code>test</code> exec, so what is documented here is what is exercised.</p>
+
+          <header class="section-head" id="package">
+            <p class="kicker"><span>Package &amp; source</span></p>
+            <h2>The ledger</h2>
+          </header>
+          <dl class="docs-package">
+            <div><dt>Version</dt><dd><code>1.0.0</code> (from <code>MANIFEST.usm</code>)</dd></div>
+            <div><dt>Licence</dt><dd>LGPL-3.0-or-later — free to run, study, share and improve; the LGPL keeps it free to link from any program.</dd></div>
+            <div><dt>Package</dt><dd><code>usm install invercargill</code></dd></div>
+            <div><dt>Provides</dt><dd><code>libinvercargill-1.so</code>, <code>invercargill-1.h</code>, <code>invercargill-1.vapi</code> + <code>.deps</code>, <code>invercargill-1.gir</code>, <code>invercargill-1.typelib</code>, <code>invercargill-1.pc</code></dd></div>
+            <div><dt>Needs at runtime</dt><dd>GLib and GObject — nothing else. The foundation carries no dependencies of its own.</dd></div>
+            <div><dt>Source</dt><dd>The repository <code>Tilo15/Invercargill</code> on the Astrologue forge (git over ssh, port 222). Free software: the licence guarantees your right to the source.</dd></div>
+          </dl>
+
+          <nav class="docs-siblings" aria-label="Built on Invercargill">
+            <p>The libraries that stand on it directly:</p>
+            <ul>
+              <li><a href="../invercargill-json/">Invercargill-Json</a></li>
+              <li><a href="../invercargill-sql/">Invercargill-Sql</a></li>
+              <li><a href="../inversion/">Inversion</a></li>
+            </ul>
+          </nav>
+
+          <nav class="docs-pager" aria-label="Project pages">
+            <p class="docs-pager__up"><a href="../">All eleven</a></p>
+            <a class="docs-pager__link docs-pager__link--next" href="../invercargill-json/">
+              <span>Next in the catalogue</span>
+              <strong>Invercargill-Json</strong>
+            </a>
+          </nav>
+
+        </div>
+      </div>
+    </section>
+
+  </main>
+
+  <footer class="site-footer">
+    <div class="container">
+      <div class="footer-grid">
+        <div class="foot-brand">
+          <img class="brand__logo brand__logo--dark" src="../../assets/img/astrologue-dark.png" width="392" height="89" alt="Astrologue">
+          <p>An operating system for change — kept for self sufficiency, resilience, and software freedom, so regular people can hold the power of general purpose computers in their own hands.</p>
+        </div>
+        <nav aria-label="The four freedoms">
+          <p class="foot-title">The Four Freedoms</p>
+          <ol class="freedoms">
+            <li><strong>Run</strong> the software as you wish, for any purpose.</li>
+            <li><strong>Study</strong> the source and change it to do your computing as you wish.</li>
+            <li><strong>Share</strong> copies so you can help others.</li>
+            <li><strong>Modify</strong> and publish your versions so everyone benefits.</li>
+          </ol>
+        </nav>
+        <nav aria-label="Site">
+          <p class="foot-title">Site</p>
+          <ul class="foot-list">
+            <li><a href="../../">Home</a></li>
+            <li><a href="../../#news">News</a></li>
+            <li><a href="../../#download">Download</a></li>
+            <li><a href="../">Developer</a></li>
+          </ul>
+        </nav>
+      </div>
+      <p class="colophon">Set in Cinzel and EB Garamond (SIL Open Font License). Astrologue is free software, licensed GPL-3.0-or-later: take it, tend it, pass it on. The libraries are LGPL-3.0-or-later, so they remain free to link.</p>
+    </div>
+  </footer>
+</body>
+</html>

+ 265 - 0
developer/inversion/index.html

@@ -0,0 +1,265 @@
+<!doctype html>
+<html lang="en-nz">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>Inversion — Astrologue Developer</title>
+  <meta name="description" content="Inversion of control for the Web-Stack: a dependency-injection container for Vala with modules, scopes and three lifecycles, wired by field injection. Free software, LGPL-3.0-or-later.">
+  <meta name="color-scheme" content="light dark">
+  <meta name="theme-color" content="#f2ebda" media="(prefers-color-scheme: light)">
+  <meta name="theme-color" content="#0a0a12" media="(prefers-color-scheme: dark)">
+  <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Ccircle cx='32' cy='32' r='27' fill='none' stroke='%23a8801f' stroke-width='4'/%3E%3Cpath d='M32 8l6 18 18 6-18 6-6 18-6-18-18-6 18-6z' fill='%23a8801f'/%3E%3Ccircle cx='32' cy='32' r='4' fill='%23003078'/%3E%3C/svg%3E">
+  <meta property="og:title" content="Inversion — Astrologue Developer">
+  <meta property="og:description" content="A dependency injection (IoC) container for Vala: three lifecycles, field injection, multi-registration and scope-local registrations.">
+  <meta property="og:type" content="website">
+  <link rel="stylesheet" href="../../assets/css/style.css">
+</head>
+<body>
+  <a class="skip-link" href="#main">Skip to content</a>
+
+  <header class="masthead">
+    <div class="container masthead__inner">
+      <a class="brand" href="../../">
+        <img class="brand__logo brand__logo--light" src="../../assets/img/astrologue-light.png" width="392" height="89" alt="Astrologue">
+        <img class="brand__logo brand__logo--dark" src="../../assets/img/astrologue-dark.png" width="392" height="89" alt="Astrologue">
+      </a>
+      <nav class="site-nav" aria-label="Primary">
+        <ul class="site-nav__list">
+          <li><a href="../../">Home</a></li>
+          <li><a href="../../#news">News</a></li>
+          <li><a href="../../#download">Download</a></li>
+          <li><a href="../" aria-current="page">Developer</a></li>
+          <li><a class="nav-cta" href="../../#download">Get Astrologue</a></li>
+        </ul>
+      </nav>
+    </div>
+  </header>
+
+  <main id="main">
+
+    <nav class="breadcrumb" aria-label="Breadcrumb">
+      <div class="container">
+        <ol>
+          <li><a href="../../">Astrologue</a></li>
+          <li><a href="../">Developer</a></li>
+          <li aria-current="page">Inversion</li>
+        </ol>
+      </div>
+    </nav>
+
+    <section class="docs-header">
+      <div class="container">
+        <p class="kicker"><span>Foundation · The wiring of the Web-Stack</span></p>
+        <h1>Inversion</h1>
+        <p class="lede">A dependency injection (IoC) container for Vala — three lifecycles, field injection, multi-registration and scope-local registrations. It is how an Astrologue web application wires itself together at startup.</p>
+        <dl class="docs-facts">
+          <div><dt>Version</dt> <dd><code>0.2</code></dd></div>
+          <div><dt>Licence</dt> <dd>LGPL-3.0-or-later</dd></div>
+          <div><dt>Package</dt> <dd><code>usm&nbsp;install&nbsp;inversion</code></dd></div>
+          <div><dt>Language</dt> <dd>Vala</dd></div>
+        </dl>
+      </div>
+    </section>
+
+    <section class="band band--plain" id="docs">
+      <div class="container docs-layout">
+
+        <nav class="docs-toc" aria-label="On this page">
+          <h2>On this page</h2>
+          <ul>
+            <li><a href="#overview">Overview</a></li>
+            <li><a href="#features">Features</a></li>
+            <li><a href="#installation">Installation</a></li>
+            <li><a href="#usage">Usage &amp; documentation</a></li>
+            <li><a href="#package">Package &amp; source</a></li>
+          </ul>
+        </nav>
+
+        <div class="docs-prose">
+
+          <header class="section-head" id="overview">
+            <p class="kicker"><span>Overview</span></p>
+            <h2>Why it exists</h2>
+          </header>
+          <p>Inversion is a dependency injection (IoC) container for Vala. Applications declare what they need; the container decides what concrete thing fills each need, and when a new instance is made versus shared. Nothing in the stack reaches out and constructs its own collaborators — the wiring lives in one place, and that place is testable.</p>
+          <p>The Web-Stack is built around it. Spry's users and permissions, Invercargill-Sql's connections and sessions, an application's own services — all are registered in a container, resolved in scopes, and injected by field. The companion library <a href="../invercargill-sql-inversion/">Invercargill-Sql-Inversion</a> exists purely to make this container and the database layer one motion.</p>
+
+          <header class="section-head" id="features">
+            <p class="kicker"><span>Features</span></p>
+            <h2>What it hands you</h2>
+          </header>
+          <ul class="docs-features">
+            <li><strong>Three lifecycles.</strong> Transient — a new instance each time; Scoped — one per scope; Singleton — one per container.</li>
+            <li><strong>Field injection.</strong> Dependencies resolve via <code>inject&lt;T&gt;()</code> in field initializers; no constructor plumbing.</li>
+            <li><strong>Multi-registration.</strong> Register several implementations for one service type and resolve them all at once.</li>
+            <li><strong>Scope-local registrations.</strong> Add registrations that only exist within a scope.</li>
+            <li><strong>Fluent API.</strong> Chained registration methods keep configuration readable.</li>
+            <li><strong>Registration metadata.</strong> Registrations carry typed metadata that other services can query — how the type provider of the data layer finds its mappers.</li>
+          </ul>
+
+          <header class="section-head" id="installation">
+            <p class="kicker"><span>Installation</span></p>
+            <h2>Getting the library</h2>
+          </header>
+          <figure class="code-plate">
+            <pre><code>usm install inversion</code></pre>
+            <figcaption>USM resolves <code>invercargill-1</code> itself.</figcaption>
+          </figure>
+          <p>From source:</p>
+          <figure class="code-plate">
+            <pre><code>git clone &lt;the Inversion repository&gt;
+cd Inversion
+meson setup build
+ninja -C build
+ninja -C build install</code></pre>
+            <figcaption>GLib, GObject and Invercargill are the whole dependency set.</figcaption>
+          </figure>
+          <figure class="code-plate">
+            <pre><code>valac --pkg inversion-0.1 my-program.vala</code></pre>
+            <figcaption>The binding pulls in its <code>.deps</code> chain automatically.</figcaption>
+          </figure>
+
+          <header class="section-head" id="usage">
+            <p class="kicker"><span>Usage &amp; documentation</span></p>
+            <h2>Quick start, from the README</h2>
+          </header>
+          <p>Define a service, register an implementation, resolve it through a scope:</p>
+          <figure class="code-plate">
+            <pre><code>using Inversion;
+
+// Define your service
+public interface Logger : Object {
+    public abstract void log(string message);
+}
+
+public class ConsoleLogger : Object, Logger {
+    public void log(string message) {
+        stdout.printf("[LOG] %s\n", message);
+    }
+}
+
+// Register and resolve
+var container = new Container();
+container.register_singleton&lt;ConsoleLogger&gt;((scope) =&gt; new ConsoleLogger())
+    .as_type(typeof(Logger));
+
+var scope = container.create_scope();
+var logger = scope.resolve&lt;Logger&gt;();
+logger.log("Hello!");</code></pre>
+            <figcaption>Verbatim from the README's Quick Start.</figcaption>
+          </figure>
+          <h3>Lifecycles</h3>
+          <p>One registration per lifetime policy:</p>
+          <figure class="code-plate">
+            <pre><code>container.register_transient&lt;MyService&gt;((s) =&gt; new MyService());
+container.register_scoped&lt;MyService&gt;((s) =&gt; new MyService());
+container.register_singleton&lt;MyService&gt;((s) =&gt; new MyService());</code></pre>
+            <figcaption>Transient: new instance every resolve. Scoped: one per scope. Singleton: one per container.</figcaption>
+          </figure>
+          <h3>Field injection</h3>
+          <p>Dependencies are injected using the <code>inject&lt;T&gt;()</code> function in field initializers — resolved automatically when the owning class is created:</p>
+          <figure class="code-plate">
+            <pre><code>public class UserService : Object {
+    private Logger logger = inject&lt;Logger&gt;();
+
+    public void greet(string name) {
+        this.logger.log(@"Hello, $name!");
+    }
+}
+
+var userService = scope.resolve&lt;UserService&gt;();</code></pre>
+            <figcaption>And <code>inject_all&lt;T&gt;()</code> brings in every implementation: <code>private Lot&lt;Logger&gt; loggers = inject_all&lt;Logger&gt;();</code></figcaption>
+          </figure>
+          <h3>Multiple implementations</h3>
+          <figure class="code-plate">
+            <pre><code>container.register_singleton&lt;FileLogger&gt;((s) =&gt; new FileLogger())
+    .as_type(typeof(Logger));
+container.register_singleton&lt;ConsoleLogger&gt;((s) =&gt; new ConsoleLogger())
+    .as_type(typeof(Logger));
+
+// Get all implementations
+var allLoggers = scope.resolve_all&lt;Logger&gt;();</code></pre>
+            <figcaption>From the README.</figcaption>
+          </figure>
+          <h3>Scopes with their own registrations</h3>
+          <figure class="code-plate">
+            <pre><code>var scope = container.create_scope();
+
+// Add a registration only available in this scope
+scope.register_singleton&lt;RequestContext&gt;((s) =&gt; new RequestContext())
+    .as_type(typeof(Context));</code></pre>
+            <figcaption>Scopes manage scoped instance lifetime and can carry local registrations.</figcaption>
+          </figure>
+          <p>The repository's <code>examples/</code> directory walks each feature end to end — <code>BasicUsage.vala</code>, <code>LifecycleDemo.vala</code>, <code>InjectionDemo.vala</code>, <code>MultiRegistration.vala</code> and <code>ScopeRegistrationDemo.vala</code> — and the library's test suite runs through <code>meson test</code> and USM's <code>test</code> exec.</p>
+
+          <header class="section-head" id="package">
+            <p class="kicker"><span>Package &amp; source</span></p>
+            <h2>The ledger</h2>
+          </header>
+          <dl class="docs-package">
+            <div><dt>Version</dt><dd><code>0.2</code> (from <code>MANIFEST.usm</code>)</dd></div>
+            <div><dt>Licence</dt><dd>LGPL-3.0-or-later — free to run, study, share and improve; the LGPL keeps it free to link from any program.</dd></div>
+            <div><dt>Package</dt><dd><code>usm install inversion</code></dd></div>
+            <div><dt>Provides</dt><dd><code>libinversion-0.1.so</code>, <code>inversion-0.1.h</code>, <code>inversion-0.1.vapi</code> + <code>.deps</code>, <code>inversion-0.1.gir</code>, <code>inversion-0.1.typelib</code>, <code>inversion-0.1.pc</code></dd></div>
+            <div><dt>Needs at runtime</dt><dd>GLib, GObject, and <code>libinvercargill-1.so</code>.</dd></div>
+            <div><dt>Source</dt><dd>The repository <code>Tilo15/Inversion</code> on the Astrologue forge (git over ssh, port 222). Free software: the licence guarantees your right to the source.</dd></div>
+          </dl>
+
+          <nav class="docs-siblings" aria-label="Neighbours in the catalogue">
+            <p>Its neighbours:</p>
+            <ul>
+              <li><a href="../invercargill-json/">Invercargill-Json</a></li>
+              <li><a href="../invercargill-sql/">Invercargill-Sql</a></li>
+              <li><a href="../invercargill-sql-inversion/">Invercargill-Sql-Inversion</a></li>
+            </ul>
+          </nav>
+
+          <nav class="docs-pager" aria-label="Project pages">
+            <a class="docs-pager__link docs-pager__link--prev" href="../invercargill-json/">
+              <span>Previous</span>
+              <strong>Invercargill-Json</strong>
+            </a>
+            <p class="docs-pager__up"><a href="../">All eleven</a></p>
+            <a class="docs-pager__link docs-pager__link--next" href="../invercargill-sql/">
+              <span>Next in the catalogue</span>
+              <strong>Invercargill-Sql</strong>
+            </a>
+          </nav>
+
+        </div>
+      </div>
+    </section>
+
+  </main>
+
+  <footer class="site-footer">
+    <div class="container">
+      <div class="footer-grid">
+        <div class="foot-brand">
+          <img class="brand__logo brand__logo--dark" src="../../assets/img/astrologue-dark.png" width="392" height="89" alt="Astrologue">
+          <p>An operating system for change — kept for self sufficiency, resilience, and software freedom, so regular people can hold the power of general purpose computers in their own hands.</p>
+        </div>
+        <nav aria-label="The four freedoms">
+          <p class="foot-title">The Four Freedoms</p>
+          <ol class="freedoms">
+            <li><strong>Run</strong> the software as you wish, for any purpose.</li>
+            <li><strong>Study</strong> the source and change it to do your computing as you wish.</li>
+            <li><strong>Share</strong> copies so you can help others.</li>
+            <li><strong>Modify</strong> and publish your versions so everyone benefits.</li>
+          </ol>
+        </nav>
+        <nav aria-label="Site">
+          <p class="foot-title">Site</p>
+          <ul class="foot-list">
+            <li><a href="../../">Home</a></li>
+            <li><a href="../../#news">News</a></li>
+            <li><a href="../../#download">Download</a></li>
+            <li><a href="../">Developer</a></li>
+          </ul>
+        </nav>
+      </div>
+      <p class="colophon">Set in Cinzel and EB Garamond (SIL Open Font License). Astrologue is free software, licensed GPL-3.0-or-later: take it, tend it, pass it on. The libraries are LGPL-3.0-or-later, so they remain free to link.</p>
+    </div>
+  </footer>
+</body>
+</html>