Commit Graph

290 Commits

Author SHA1 Message Date
a860557e91 docs(android): M12 phase 1 as landed
The colors phase, shipped as Android-app#34. Seven notes, of which three are
departures the build forced rather than choices it made:

ColorScheme has no equals in material3 1.3.0, so AC-1's "full equality, not a
spot check" is a field-by-field compare by reflection over all 36 color roles,
against a verbatim copy of the pre-M12 scheme held in the test.

ShardPillFg is derived from --accent-bright rather than being a sixteenth
token, under the rule 5.1 already states for ShardOnCta.

An instance with an env accent and no theme_visual row loses its accented CTA
buttons, because brand.accent now seeds --accent alone. That is 5.1's "wrong
twice over" being corrected, and it is the one thing on screen that moves.

PLAN.md 9's M12 entry records phase 1 landed.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-08 04:55:11 -05:00
beae4b21dd Merge pull request 'docs(android): M12 phase 0 as landed, and phase 7 cancelled' (#112) from docs/android-theming-nav-phase-0 into edge
Reviewed-on: #112
2026-08-08 07:01:55 +00:00
11999c4bcd docs(android): M12 phase 0 as landed, and phase 7 cancelled
Two records, one milestone.

Phase 7 (reading nav_admin / nav_player) is cancelled. Section 6.4 already
made the case against it and scheduled it last so the call could be taken on
its merits with the rest working: the two authenticated navs reach four app
rows between them, which does not pay for a new authenticated fetch, a
session-keyed cache and its teardown. The app therefore makes no authenticated
settings call at all, and the player and staff drawer rows keep their coded --
and so localized -- labels, which is the one thing given up. Section 4's locked
decision, section 3's endpoint row and section 5.5's lifecycle paragraph are
amended to match so the doc does not contradict itself, and section 8 gains
the standing "Phase 7, cancelled" note.

Phase 0 (the contract and the appearance store) landed as Android-app#33, and
its "as landed" notes record two departures from what section 8 specified:

- `theme` is modeled as a raw JsonElement rather than Map<String,String>?.
  kotlinx fails the decode of the whole object on a value of an unexpected
  kind, and `theme` shares its payload with `brand` and `push` -- so one odd
  token would have blanked the branding and dropped the push relay URL, the
  opposite of section 2. It is coerced field-by-field instead.
- A failed refresh keeps the last good appearance rather than falling back to
  NONE. Section 5.5 said best-effort; the distinction it did not draw is that
  a moment of no connectivity on resume must not repaint a themed shard back
  to the shipped defaults.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-08 01:58:48 -05:00
e7dea29885 Merge pull request 'docs(android): build contract for admin theming and navigation parity (M12)' (#111) from docs/android-theming-nav-plan into main
Reviewed-on: #111
2026-08-08 06:45:16 +00:00
b150e354a8 Merge pull request 'docs(tree): sync website/PROJECT_TREE.md' (#110) from chore/sync-website-tree into main
Reviewed-on: #110
2026-08-08 06:45:00 +00:00
e86b04567f docs(android): build contract for admin theming & navigation parity (M12)
The website merged runtime admin theming, brand assets and nav overrides to
main (website#126 / docs#109). The app reads exactly one field of it --
brand.accent -- and renders a hardcoded APP_MENU, so an admin who re-skins the
site and restructures the header sees none of it on the phone.

Adds docs/android/THEMING_AND_NAV.md as the design of record for M12, and the
PLAN.md §9 entry that anchors it. Plan only: no app code, no backend work.
Everything consumed is already live on website/main.

The points that shaped it:

- The app's ui/theme/Color.kt palette is already, value for value, the
  runic-gateway preset -- M5 was drawn from the same theme.css the preset was
  later extracted from. So "an untouched instance is unchanged" carries over as
  a testable ColorScheme equality assertion, not an approximation.
- Radii apply as a ratio against that baseline, not as literal dp. The app's
  Shapes came from the M5 mockup and genuinely differ (medium 12dp vs
  --radius-card 10px); a literal mapping would restyle the untouched app the
  day this ships, and copying the app's scale into the server would be a second
  source of truth.
- Fonts are bundled, not downloadable: the Play Store font provider makes a
  de-Googled device fall back silently. Seven families join the bundled Cinzel.
- Nav overrides are keyed by website paths, so the app needs a path -> route
  table -- the one new cross-repo coupling here. An override for a path the app
  does not surface in its menu is ignored: a nav override may never introduce
  navigation.
- The gates are untouched. MenuAccess and MenuEntry.feature still run after the
  merge, so hidden:false cannot un-hide what a role or the shard's visibility
  config withholds.
- Read the resolved theme/brand fields, never the raw theme_visual/brand_assets
  rows that ride along in the same payload -- re-deriving a palette from them
  would be a second resolveThemeTokens in Kotlin, guaranteed to drift.

Nine phases into a fresh edge in both repos, reaching main as one edge -> main
merge, the same shape the website side used. Phase 0 must change nothing on
screen. Phase 7 (the authenticated navs) is marked optional: nav_player reaches
two app rows and nav_admin two, which is a thin return for a new authenticated
fetch and its cache teardown.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TgfKv5cz5pbY3dPeofSE5a
2026-08-08 01:39:52 -05:00
runic-docs-bot
3aed6bca17 docs(tree): sync website/PROJECT_TREE.md from RunicGateway/website@265042e [skip ci] 2026-08-08 06:19:50 +00:00
518f1e0449 Merge pull request 'docs(website): theming &amp; navigation, complete (edge → main)' (#109) from edge into main
Reviewed-on: #109
2026-08-08 06:09:06 +00:00
2e955f1e9c Merge pull request 'docs(website): phase 10 — public nav sections and added links, and the §7 amendment' (#108) from docs/theming-nav-phase-10 into edge
Reviewed-on: #108
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-08-08 06:00:18 +00:00
2784cad6e4 docs(website): phase 10 — public nav sections and added links, and the §7 amendment
Records the capability asked for before the edge -> main cutover: dropdown
sections in the public header, with the coded entries organised into them and
admin-authored links alongside.

§7 is amended rather than quietly contradicted. It said the override layer
"cannot introduce a `to` that is not already in the hardcoded NAV array"; that
remains true of every CODED entry, and the restated constraint spells out what an
added link may be — a same-origin path, carrying no gate of its own, advertising
a route rather than granting one — plus why the property is structural: coded
entries live in a map keyed by routes the base array declares, and everything
that can name an arbitrary path lives in `links`, where the rule is applied.

§6.4 gains the { items, sections, links } wrapper, including the two properties
worth knowing: a bare map still reads as the items map, and a nav with no
sections still stores one.

§9 gains three acceptance criteria — the empty dropdown does not render, an added
link cannot leave the origin, and deleting a section returns its entries to the
top level rather than removing them.

"Phase 10 as landed" records why the Public tab needed its own tree editor, why
moving between containers stayed a dropdown rather than a cross-container drag,
why the menu opens on click and its trigger is not a link, and the palette-vs-full-nav
bug this surfaced in the phase 6-8 save path.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-08 00:42:39 -05:00
d7dd0e5078 Merge pull request 'docs(website): theming phases 6-8 as built, and phase 9 cancelled' (#107) from docs/theming-nav-phase-6-8 into edge
Reviewed-on: #107
2026-08-08 05:11:45 +00:00
5c24ff1378 docs(website): theming phases 6-8 as built, and phase 9 cancelled
Marks the nav wiring and the builder UI landed, and records the five places the
build differed from the design:

- The server had no way to store a nav row. The design scoped 6-8 as client
  work, but updateSettings would have written a nav object as "[object Object]"
  — a save that 200s and does nothing, for ever.
- The server deliberately cannot check that a `to` exists: the base NAV arrays
  are client constants, and a server-side copy would be a second source of truth
  for navigation. Shape is the server's question, membership the client's.
- `hidden: false` is accepted and never stored, so hiding stays subtractive.
- The nav editor cannot be hidden, enforced in three places.
- Orders are written only when something actually moved, compared against the
  base restricted to the rows the editing admin can see.

Phase 9 (hue-carrying rgba literals + Parchment) is cancelled rather than
deferred. The finding that motivated it is kept as the record: those literals
carry a hue, so they are a rough edge in the three dark presets and not only a
blocker for a hypothetical light one.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-08 00:02:46 -05:00
2a8b9d5748 Merge pull request 'docs(website): theming phase 5 as built — brand assets and the cached HTML shell' (#106) from docs/theming-nav-phase-5 into edge
Reviewed-on: #106
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-08-08 02:05:03 +00:00
97cb8be2d5 docs(website): theming phase 5 as built — brand assets and the cached shell
Records Phase 5 of THEMING_AND_NAV.md as landed and documents the new route
and the shell lifecycle in BACKEND_DESIGN.md.

Where the build differed from the design: the upload is one admin-only call
that writes the settings row too (rather than the generic staff upload plus a
PUT, which would leave unreferenced files and let editors change the site's
identity); brand_assets needed a validator of its own because these are the
only settings values written straight into HTML as URLs; the shell cache
carries a TTL as well as explicit invalidation because it is per process; and
the logo went into all six MoonDot surfaces rather than three.

Also notes what was deliberately left alone: the shell's title and description
still come from BRAND_NAME rather than the admin-set site_title, and fixing
that would change the served shell for instances with no brand_assets row —
which is exactly what the phase's acceptance criterion forbids.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-07 20:10:16 -05:00
ce6f5b8788 Merge pull request 'docs(website): theming & nav phases 3-4 as built' (#105) from docs/theming-nav-phase-3-4 into edge
Reviewed-on: #105
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-08-08 00:21:41 +00:00
e669243aca docs(website): theming phases 3-4 as built
Records where the build diverged from the design and why.

- The presets do not live in theme.css as [data-theme] blocks. Section 6.2 is
  marked superseded and a "Phases 3-4 as landed" section explains the inline
  --accent precedence problem that forced server-side resolution.
- Section 4.5's accentInt fix is struck through: getPublic() never exposed
  accentInt, and Discord embeds are colored by a separate process reading env,
  so there was nothing per-request to recompute. Replaced with what was
  actually done -- the bot fetching the effective accent.
- Phase 9 re-scoped. Applying Fantasy on a live instance showed the section 4.8
  rgba literals carry a hue, not just a light/dark assumption, so the dark
  presets need that promotion too.
- BACKEND_DESIGN.md: the new /settings/theme/options route, the `theme` block
  on /public/settings, effective values in the brand block, theme_visual
  validation on PUT /admin/settings, route count 225 -> 226.
- api-route-inventory.json regenerated from the manifest.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-07 19:16:47 -05:00
09e6ffd67c Merge pull request 'docs(website): theming & nav phases 0-2 as built' (#104) from docs/theming-nav-phase-0-2 into edge
Reviewed-on: #104
2026-08-07 23:25:06 +00:00
252733644b docs(website): theming & nav phases 0-2 as built
Matches RunicGateway/website's phases 0-2 of THEMING_AND_NAV.md.

BACKEND_DESIGN.md:
- The new /settings router group and its one route, plus why it is a fifth
  group rather than a route on an existing one.
- DELETE /admin/settings/:key in the admin route table, with the allowlist and
  why reset deletes instead of writing.
- The five unseeded theming/nav keys under the settings schema: absence of the
  row is the "use the default" state, values are TEXT so consumers parse, and
  malformed reads as absent.
- Route count 215 -> 225.

THEMING_AND_NAV.md:
- Phases 0-2 marked landed, with an "as landed" section recording the three
  things the design left open: where /settings/nav lives, where
  parseJsonSetting lives, and the exact 23-declaration radius promotion.
- The nav merge util's ordering rules, settled by the implementation: an
  untouched item keeps its index as its sort key, an explicit order wins a tie
  against a coincidental index, equal explicit orders keep code order, and
  `group` is honored only when it names an existing section.
- All four PR pairs target `edge`; the feature reaches `main` as one merge.

api-route-inventory.json: resynced from server/routes.manifest.json. Picks up
the two new routes plus eight that were already missing from the mirror since
the Protocol 3.0 cutover (shard clilocs, market, points).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-07 18:15:50 -05:00
6398285a13 Merge pull request 'docs(website): add theming & nav build contract' (#103) from docs/theming-and-nav-plan into main
Reviewed-on: #103
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-08-07 22:07:47 +00:00
d33064e8d7 docs(website): add theming & nav build contract
Corrects the design doc against the current codebase and locks the open
decisions, in the same shape as HERO_EDITOR.md (locked decisions ->
corrections to reality -> phased build).

Blocking gaps found in the design doc:
  - no delete path exists for a settings row, which every "reset to
    defaults" in the feature depends on
  - editors/moderators/players have no endpoint to read their own nav
    overrides (GET /admin/settings is admin-only)
  - renderIndexHtml runs once at boot, not per request
  - settings values are JSON strings, not objects
  - getPublic().brand is a cross-repo contract the Android app and
    Discord embeds theme from; new keys would silently bypass it

Locked: effective values resolved server-side into getPublic().brand;
radius + shadow tokens only (spacing/border cut); radius tokens seeded at
today's real values so the promotion is a no-op; three dark presets in v1
with Parchment deferred; 12-option font shortlist across 8 web families
in one request; PNG-only favicons.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-07 17:04:54 -05:00
6d25279b32 Merge pull request 'docs(installer): lead with the installer now that it is released' (#102) from docs/installer-first-setup into main
Reviewed-on: #102
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-08-07 21:34:25 +00:00
5197c2c281 docs(installer): lead with the installer now that it is released
v0.1.0 shipped on 2026-08-07, so every doc that said "not released yet"
was wrong the moment the cutover merged.

- INSTALL.md: replace the pre-release status banner. The installer is the
  path the guide leads with; Appendix A is reframed as supported-not-
  deprecated, for hosts that cannot run the binary, operators who want to
  place files themselves, and development from a working tree.
- PLAN.md: status is Shipped, both cutover gates recorded as met (incl.
  the Windows 1053 handshake bug the real SCM run found), Phase 5 table
  and 5.4 closed out.
- README.md: point anyone setting up a shard at INSTALL.md first.
- link/link-README.md: mark the pre-split snapshot as historical, so its
  deploy.ps1 instructions stop reading as the setup path.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-07 16:05:39 -05:00
c73db117f5 Merge pull request 'docs(tree): sync installer/PROJECT_TREE.md' (#101) from chore/sync-installer-tree into main
Reviewed-on: #101
2026-08-07 20:29:37 +00:00
runic-docs-bot
22cf093f97 docs(tree): sync installer/PROJECT_TREE.md from RunicGateway/installer@1173a10 [skip ci] 2026-08-07 20:28:25 +00:00
4a35e86bc8 Merge pull request 'docs(installer): bundles publish to a branch, releases only tag' (#97) from docs/bundles-branch into main
Reviewed-on: #97
2026-08-07 19:15:33 +00:00
9889eefb0f Merge pull request 'docs(tree): sync installer/PROJECT_TREE.md' (#100) from chore/sync-installer-tree into main
Reviewed-on: #100
2026-08-07 19:15:06 +00:00
bc97e5d221 Merge pull request 'docs(tree): sync link/PROJECT_TREE.md' (#99) from chore/sync-link-tree into main
Reviewed-on: #99
2026-08-07 19:12:55 +00:00
runic-docs-bot
5c9aa2891c docs(tree): sync installer/PROJECT_TREE.md from RunicGateway/installer@09eafe2 [skip ci] 2026-08-07 19:12:12 +00:00
runic-docs-bot
c54dcb47f5 docs(tree): sync link/PROJECT_TREE.md from RunicGateway/link@67d7800 [skip ci] 2026-08-07 18:53:32 +00:00
18dfceae73 Merge pull request 'docs(installer): correct the Windows service decision, and the 1053 advice' (#98) from docs/windows-service-1053 into main
Reviewed-on: #98
2026-08-07 18:52:35 +00:00
7c1a88febb docs(installer): correct the Windows service decision, and the 1053 advice
PLAN.md §8 recorded that `sc create` against the plain console sidecar worked
and needed no change to `link`. The first Windows install disproved it: 1053,
"a timeout was reached (30000 milliseconds) while waiting for the service to
connect", with SERVICE_EXIT_CODE 0.

The premise was a false symmetry with systemd. systemd supervises any
foreground process; the Windows SCM supervises only one that calls
StartServiceCtrlDispatcher within ~30 seconds. Record the reversal and what it
costs: link gains a Windows service entry point, kept at the edges so the whole
sidecar stays shared and Cargo builds neither Windows crate for Linux.

INSTALL.md:
- Troubleshooting gains a 1053 row naming the real cause (a sidecar older than
  v1.2.0) and the two tell-tales that distinguish it from a crash: exit code 0,
  and a foreground run of the same binary working fine.
- The existing "stops immediately" row said the same wrong thing; it now covers
  the genuine-crash case only, and points at the log file and journalctl.
- §3 and Appendix A4 document the service log, and A4 states the version floor.
- Fixes a literal 0x08 byte in the backups path row, which rendered as
  `%ProgramData%\RunicGatewayackups\` — the backslash had been eaten.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-07 13:37:29 -05:00
ddcfb5de29 docs(installer): bundles publish to a branch, releases only tag
Two corrections to §7.1, both forced by the first compose run that ever
had a bundle to write (org lead, 2026-08-05).

The section said bundles are committed to `main` and that this "needs no
new branch-protection exception: release.yml's version-bump commit
already requires the CI user to be able to push to main". Both halves
were wrong. `main` is protected and declines the push, and release.yml
had never pushed anything: its bump step has never executed in any repo
carrying it, because an empty template expression written literally in
one of its comments makes the runner fail to build the step and skip it
without failing the job. The tags exist because Gitea's release API
creates one when it publishes. The assumption that a working push path
already existed had never been tested by anything.

Bundles now publish to a `bundles` branch at its root, which keeps every
property the original choice was for -- reviewable diff, git history of
the compat matrix, plain anonymous raw URLs, no credentials on the shard
host -- and needs no exception. The release workflows are tag-only for
the same reason, as servuo-plugins has always been: the version is still
written into Cargo.toml before building so a released binary
self-reports correctly, but is not committed back.

Also updated: the raw URLs in INSTALL.md Appendix A1 and in Phase 0.3's
as-built note.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-05 17:22:16 -05:00
00d476c00b Merge pull request 'docs(installer): Phase 5 — scope, the §5.3 correction, and the operator guide' (#96) from docs/installer-phase5-scope into main
Reviewed-on: #96
2026-08-05 17:52:20 +00:00
ad7defd471 docs(installer): document aarch64 and the upgrade backup in INSTALL.md
PLAN.md §5.4. The operator guide is the specification of the run, so
these are part of building the phase rather than a write-up after it.

- The download list gains runicgateway-installer-linux-aarch64, with
  `uname -m` as the way to tell, and says plainly that there is no macOS
  and no Windows-on-arm build: the shard dials the sidecar out on
  loopback, so the two share a host, and no ServUO host is either.
- Appendix A3 names the arm64 sidecar asset for the by-hand path, and
  points at the bundle from A1 for the version rather than the one
  written in the example.
- §7's `update` says what a backup is, when one is taken and when one is
  not, and that restoring is the operator's to do -- the guide already
  promised their Bridge.cfg edits survive, and this is the same promise
  for the .cs file they edited that gets overwritten by design.
- --no-backup joins the flags table; --purge's row and the uninstall
  table now name backups alongside the config, the database and the
  cached patch set.
- The paths tables and doctor's sample output gain the backups
  directory and its row.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-05 05:53:11 -05:00
e18eec8957 docs(installer): correct §5.3's trigger and record what building §5.2 found
Two things this section got wrong, both found by implementing it.

§5.3 said a backup is taken for an update and for an install over an
existing record, because "a first install overwrites nothing". That is
not true of a tree deployed by hand per INSTALL.md Appendix A2 -- the
path this project recommends while the binary is unreleased. There the
first install finds .cs files that differ, plans them as Change, and
overwrites them with no prior record anywhere to notice. The rule is
now the direct one: back up whenever the run is about to overwrite
something. A genuine first install onto a clean tree still writes
nothing, because there is nothing to copy.

Also records that sidecar.toml joins a backup rather than causing one
(nothing rewrites it, so triggering on it would leave a dated directory
after every no-op update), and that the directory is created lazily
with the manifest written last, so an interrupted run can neither be
mistaken for a backup nor evict a good one.

§5.2 gains what its cross-builds turned up: neither crate builds with
the arm64 compiler alone. gcc-aarch64-linux-gnu only recommends
libc6-dev-arm64-cross while both release workflows install with
--no-install-recommends, so the C in each crate -- bundled SQLite under
sqlx, ring under ureq's rustls -- fails on a missing libc header while
every Rust dependency compiles fine.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-05 05:49:09 -05:00
38f83ad2e0 docs(installer): settle Phase 5's scope
Phase 5 was sketched as four items; two of them are dropped rather than
deferred, because what stops them is an ownership conflict that does not
improve with time (org lead, 2026-08-05).

- No .deb and no MSI (5.1). A .deb under link's release would own the
  binary, the systemd unit and the service user -- the three things
  service.rs writes, hardens and removes and install.json records, so
  uninstall would leave a dpkg-installed-but-broken package and an
  apt upgrade would make doctor report drift nobody caused. The
  binary-only variant buys apt-managed upgrades of one file, which
  update already does from a protocol-checked bundle. An MSI
  contradicts "the installer does not install itself" and adds a second
  uninstall path beside the verb that owns install.json, the cached
  patch set and the ServUO-tree report.
- Linux aarch64 for both components (5.2), in the order the bundle CI
  forces: it hard-fails on an unrecognized link asset name and asserts
  the platform keys present, so the name is taught on main first, link
  publishes, the key becomes required, and only then does the crate on
  edge learn it. bundle.yml is never edited on edge, so the cutover
  merge has nothing to conflict over.
- Backup before overwrite (5.3), scoped by what cannot be fetched
  again: not the binary or the overlay files, and not the database
  (store.rs is CREATE TABLE IF NOT EXISTS over shard state the sweeps
  repopulate -- a cache with a schema), but an operator's edits to a
  deployed .cs file, which Phase 1 overwrites by design, and
  sidecar.toml, whose token the website already holds.
- The docs a first release invalidates (5.4), including the repo README
  still announcing Phase 1 four phases later.

Also records that the Windows SCM smoke was attempted on 2026-08-05 and
stopped at its first check on an unelevated shell, so that half remains
entirely unexecuted.

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 8818c06f1d)
2026-08-05 05:20:26 -05:00
3eb9fa8653 Merge pull request 'docs(installer): put Phase 5 before the cutover, and flag Windows SCM as untested' (#95) from docs/installer-polish-before-cutover into main
Reviewed-on: #95
2026-08-05 10:12:17 +00:00
3a6b9196fa docs(installer): put Phase 5 before the cutover, and flag Windows SCM as untested
Two decisions from the org lead, recorded in the design of record.

Phase 5 (packaging polish) now runs BEFORE the edge -> main cutover
rather than after it. The original order assumed the cutover would cut a
v1 and packaging would follow as a v1.x, but this phase changes the
release layout itself: shipping first would mean a first release that is
immediately superseded, and operators who downloaded a bare binary being
told to re-download a package. Deferring costs nothing — nothing is
published from `edge`, and INSTALL.md's Appendix A is the supported path
meanwhile.

The cutover therefore has two entry criteria, stated in the status
header and at Phase 5: packaging polish, and the Windows SCM half being
verified on a real host. The second is called out explicitly because
`sc create`, the virtual service account, the failure actions and the
token-file ACL have still never been executed anywhere — and running the
systemd half for real is precisely what turned up a bug no unit test
had. Nothing should be released while the only untested code is the half
that registers a service.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-05 05:00:45 -05:00
c47e0fb303 Merge pull request 'docs(installer): record the first real systemd verification' (#94) from docs/installer-systemd-verified into main
Reviewed-on: #94
2026-08-05 09:30:29 +00:00
33fadbd254 docs(installer): record the first real systemd verification
Phase 2 shipped service registration that had never been executed: a
relocated test run deliberately skips it, `sc create` needs elevation,
and systemd needs a Linux host. It has now been run for real on a
privileged Debian 12 container with systemd as PID 1 — unit written and
enabled, service up as the unprivileged runicgateway user, sidecar.toml
600 and owned by it, database under /var/lib (so the UOLINK_DB_PATH pin
works), /health answering protocol 3, and uninstall taking the service,
unit, binary and account away while leaving the config, the database and
the whole ServUO tree alone.

That surfaced one bug only a real service host could show — user_created
was recorded per-run rather than as state, so an identical re-run
rewrote install.json and uninstall silently left behind the account the
installer had created (installer#8). Recorded here with the reason it is
invisible on Windows.

Also notes what is still unverified: the Windows SCM half, which needs an
elevated shell this machine's automation does not have.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-05 04:02:16 -05:00
1dc6084bf7 Merge pull request 'docs(installer): record Phase 4 — doctor, update and uninstall as built' (#93) from docs/installer-phase4 into main
Reviewed-on: #93
2026-08-05 08:49:12 +00:00
ecef87f120 docs(installer): record Phase 4 — doctor, update and uninstall as built
The installer crate now implements the whole command surface INSTALL.md
published before the binary existed, so this records what Phase 4 turned
out to be and corrects two places where the plan and the guide had drifted
apart.

PLAN.md
  - Status header: Phases 1–4 are on `edge`; the edge → main cutover now
    cuts a binary that does everything INSTALL.md describes, with Phase 5
    being packaging polish rather than capability.
  - A Phase 4 "as built" section: why `update` is the install pipeline in
    a different mode rather than a second implementation, why it neither
    reprints the token nor stays quiet about a protocol change, the tier's
    scope under `update` (re-resolve what was applied, without re-asking;
    name what is new), how `doctor` asks the binary the way the service
    does, the exit-code rule and why a stopped shard is a ⚠ while a
    running one that has not dialed in is a ✗.
  - §5's uninstall table: the cached patch set and patches/originals/ move
    from "removed" to "kept". The report that command prints tells the
    operator to diff against those originals — deleting them made the
    advice impossible to follow within one command's output. `--purge`
    removes them.

INSTALL.md
  - §2: exit codes stated (`doctor` and `uninstall` use 1 for a completed
    run that found something wrong), `--patches` now applies to `update`,
    `--yes` means yes on `uninstall`, `--purge` covers the patch cache.
  - §7 doctor: the real row set, what ✓/⚠/✗ mean, that it writes nothing
    and is safe to run with the shard up.
  - §7 update: it updates the tree install.json names, needs the shard
    stopped, does not reprint the token, calls out a protocol change, and
    what it does and does not do with the patch tier.
  - §7 uninstall: what survives, that edited files are flagged in the
    listing, the confirmation's default, and where the report file lands.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-05 03:00:45 -05:00
706b450828 Merge pull request 'docs(installer): record Phase 3 — the patch tier as built' (#92) from docs/installer-phase3 into main
Reviewed-on: #92
2026-08-05 01:07:46 +00:00
42e6f3a0cb docs(installer): record Phase 3 — the patch tier as built
PLAN.md gains a "Phase 3 as built" section covering the decisions the plan had
left open, and §2.2, §2.2.1 and §7.0 are brought in line with what shipped:

* The engine is fully native. §2.2.1 wrote rung 1 as "apply verbatim with
  git apply", but §1 chose the release tarball so there would be no git on the
  shard host, and rung 2 needs a native applier anyway. Rung 1 keeps its
  stronger verdict and shares rung 2's write path. On the real files this is
  not academic — the shipped patches are CRLF and two of their three targets
  are LF, so git apply refuses patches the installer places correctly.

* §7.0 documents `patch_tier` in the overlay manifest. Which patches form one
  unit, which companion follows which, whether a core rebuild is needed and
  what declining costs are not derivable from a diff, so the release declares
  them and adding a patch regenerates metadata rather than an installer.

* §2.2 gains the pre-image cache and the widened patch cache, and §2.2.1 gains
  the second, per-feature level of the all-or-nothing rule.

INSTALL.md's illustrated tier output is replaced with the real thing, the
status banner now says `install` is complete, §3's path tables list
patches/originals/, and Appendix A2 names the line-ending trap that makes
git apply refuse a patch whose region is visibly untouched.

Refs: RunicGateway/installer#6, RunicGateway/servuo-plugins#10

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 19:57:42 -05:00
5ae53d287f Merge pull request 'docs(installer): record Phase 2 as built — sidecar install and service' (#91) from docs/installer-phase2 into main
Reviewed-on: #91
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-08-04 20:44:59 +00:00
3f12e5f49c docs(installer): record Phase 2 as built — sidecar install and service
Phases 1 and 2 now live on the installer repo's `edge` branch, so PLAN.md's
status, the config-path section, and the operator guide all move with them.

PLAN.md
- Status: phases 1 and 2 built. The `edge -> main` cutover now follows Phase 3
  rather than Phase 2, because INSTALL.md §4 describes the patch tier as part of
  the run and a release that answers "not implemented" to all of it is the same
  half-capable binary that kept Phase 1 off `main`.
- §2.3: the service definition always pins the config path, but only Linux pins
  the database. On Windows config and data share a directory, so the sidecar's
  own anchoring rule already lands it correctly — and `sc.exe` offers no
  per-service environment, only a machine-wide one that every process inherits
  and that outlives an uninstall.
- New "Phase 2 as built" section: the virtual service account, the config
  lockdown and why its two halves straddle registration, `--verify` running no
  part of the sidecar half, the protocol check against the installed binary,
  `RUNICGATEWAY_STATE_DIR` relocating the binary and suppressing service
  registration, degrading to a printed recipe with no root/LocalSystem fallback,
  and the token never entering install.json.
- §8 question 1 (Windows service mechanism) resolved: `sc create`, as
  recommended — plus the service identity the recommendation did not anticipate.

INSTALL.md
- Status banner: what is built, and that the patch tier is the remaining gap.
- §2: the illustrated run matches the sidecar block the binary actually prints.
- §3: a table of how each platform pins config and database, the dedicated
  service account on both, and the fact that sidecar.toml's permissions are
  restricted because it holds the auth token.
- Appendix A4: the Windows recipe now matches what the installer does —
  `--config` in binPath (single-quoted so PowerShell keeps the inner quotes),
  `obj=` for the virtual account, the icacls lockdown before and grants after,
  and no machine-wide environment variables.
- Troubleshooting: a row for a run that could not register a service, and one
  for a service that starts and immediately stops.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 15:40:26 -05:00
ce02abbc11 Merge pull request 'docs(installer): record Phase 1 as built' (#90) from feat/installer-phase-1 into main
Reviewed-on: #90
2026-08-04 20:00:56 +00:00
4bdc764742 docs(installer): record Phase 1 as built
PLAN.md moves from "Phase 0 complete, no code exists" to "Phase 1 built, on
edge", and the Phase 1 section gains an "As built" block in the same shape as
the Phase 0 entries — covering the decisions that were not already settled by
the design: why the crate lands on `edge` instead of `main`, why the library
target is not named after the binary (Windows UAC installer detection makes
`cargo test` unrunnable under that name), the dependency choices that follow
from the MinGW cross-build, path-based rather than name-based shard-running
detection, reading ServUO's version from Server/AssemblyInfo.cs, and the two
rules the smoke test corrected — install.json recording a state rather than the
run's verb, and the Bridge.cfg keep comparing against the last hash deployed
rather than the last hash seen.

INSTALL.md gains the same status note and one troubleshooting row: Windows
elevates the binary on launch because its file name contains "install", which
is expected and needs no action beyond running from an elevated shell.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 14:58:46 -05:00
a98fceb4bb Merge pull request 'docs(installer): review the patched region, not the whole-file hash' (#89) from docs/installer-patch-region-review into main
Reviewed-on: #89
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-08-04 19:19:14 +00:00
5a6cb58a33 docs(installer): review the patched region, not the whole-file hash
The patch tier refused on a whole-file hash mismatch, which is the wrong
question: the three patches touch three small regions of three large files, so
an operator who edited Logging.cs somewhere else entirely was handed a manual
patch job they did not need. Hand-modified shards are the norm, so that refusal
covered most of the audience.

Replace the single hash test with a four-rung ladder (PLAN §2.2.1), cheapest and
safest first:

  0  post-patch text already present  -> no-op, keeps re-runs idempotent
  1  whole file matches the pre-image -> apply verbatim
  2  file differs, patched region is still byte-identical -> apply at the
     matched offset
  3  anything else -> do not touch the file; print the hunk to apply by hand

Rung 2 needs no new metadata: a unified diff already carries the stock text of
the region it edits (context lines plus the '-' lines). Guardrails keep it from
becoming a fuzzy apply -- exact match with only CRLF/trailing-whitespace
normalisation, exactly one occurrence or it fails, line numbers advisory only,
and all-or-nothing per patch file so a half-patched EventSink.cs cannot happen.
install.json records which rung applied each patch, and doctor and uninstall
report it.

This retires the blanket 57.4-only version gate, so PLAN gains §2.2.2 to draw
the line the ladder does not: content matching is a mechanical guarantee about
where text lands, not a support commitment. 57.4 stays the only supported
version. A non-57.4 tree may attempt the tier, but unsupported, untested and not
guaranteed -- behind a loud banner, a prompt defaulted to no, and its own
--patches-unsupported-servuo flag, because a bare --patches can be hit by
accident in a copied script. The unsupported marker persists into install.json,
every later doctor run, and the uninstall report.

INSTALL.md gets the operator-facing half: a block-quoted warning naming the
silent-script-build failure mode, the updated prerequisite row, prompts and flag
table, and a sample run showing all three outcomes.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 14:17:33 -05:00