Commit Graph

202 Commits

Author SHA1 Message Date
3a1bbdd165 Merge pull request 'docs(link): the Protocol 3.0 cutover (v3.md order 6)' (#72) from docs/protocol-3-cutover into edge
Reviewed-on: #72
2026-07-30 03:03:10 +00:00
32def88c4e docs(link): fill in the cutover PR numbers
The order-6 row was written before the seven PRs existed. Same follow-up as the
cliloc row got.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-29 18:09:50 -05:00
71207cef16 docs(link): the Protocol 3.0 cutover (v3.md order 6)
INTEGRATION.md was written for the window that just closed -- it told integrators
the version had NOT been bumped yet and that a sidecar on `edge` reports 2 while
already carrying v3 kinds. That guidance is now wrong in the direction that
matters, so the version section states 3 (header, /health, ws.hello, the 409
example and the §8 worked example) and replaces the "until then" paragraph with
what a v2 integration actually has to do to upgrade: change the constant it
sends, and nothing else, because nothing that existed in v2 changed shape.

v3.md gains §4.1 for what the bump touches and, more importantly, WHY the
website's boot migration is gated on a marker row: schema.sql is re-run on every
boot and uo_link_config.protocol is admin-editable, so an ungated UPDATE would
silently un-pin an operator running an older sidecar. That is the one piece of
the cutover a reader could not infer from the code being one constant.

Progress tables: 5b done, 6 in review.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-29 18:04:00 -05:00
cdea1aa7cd Merge pull request 'docs(link): the player-vendor marketplace (Protocol 3.0 §8)' (#71) from docs/vendor-listing into edge
Reviewed-on: #71
2026-07-29 20:02:31 +00:00
6ce60a82c3 docs(link): the player-vendor marketplace (Protocol 3.0 §8)
Documents order 5b across the four repos, and records what building it changed
about §8 as designed.

- NEW website/MARKETPLACE.md — the operator guide: what the pages must say out
  loud and why, the privacy contract (the player's in-game Vendor Search toggle
  wins, and no admin setting overrides it), the Bridge.cfg knobs and how they
  trade against each other, and the measured sweep costs.
- INTEGRATION.md — catalog entry for vendor.listing / vendor.listing.remove with
  its six consumer gotchas, and the GET /market REST section (the sidecar's only
  paged read, and why it orders by serial rather than shop name).
- BACKEND_DESIGN.md — shard_vendors / shard_vendor_items, the routes, and the
  marketplace search as the only rate-limited public read.
- SHARD_VISIBILITY.md — why the market's fields default to Everyone (the in-game
  gump already shows exactly that set), why location is one setting covering
  four things, and why hiding the owner name without the owner id achieves
  nothing.
- PLAN.md — the amortized round-robin as the one sweep pattern the bridge did not
  previously have, and an update to §7's cliloc note: pushing name resolution to
  the plugin was never an option, because ServUO cannot read a modern client's
  compressed cliloc files either.
- v3.md §8.8 — the four things the build settled differently, chief among them
  that §8.1's FLAT location payload would have made Part A's pre-wired
  market.location rule inert, exactly like the characterName miss one part
  earlier.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-29 09:52:14 -05:00
70d49b7792 Merge pull request 'docs(link): the cliloc table, and why §8.6's recommendation was not implementable' (#70) from docs/cliloc-table into edge
Reviewed-on: #70
2026-07-29 11:58:26 +00:00
ee0c146d7a docs(link): cliloc overlays for shard-added and shard-edited items
Shards edit items and add new ones, carrying cliloc ids no stock client table
has. The cliloc table now reads a SET of sources rather than one file — a base
plus every operator-maintained overlay under `custom/`, hash-gated together and
re-read on every boot, exactly as §6 reads the ServUO tree. Matching docs for
website #115.

website/CLILOCS.md gains a "Shard-added and shard-edited items" section: the
directory layout, merge precedence, the per-source breakdown an operator uses
to confirm an overlay took effect, and why `custom/` is a convention we chose
rather than one discovered from the shard — ServUO has no server-side notion of
a custom cliloc, they live in the patched client a shard distributes and
nothing in the tree declares them. Scale recorded: the live shard's script tree
references 16,434 cliloc ids and only 37 are absent from stock, which is why
this is an overlay and not a second table.

"Why there is no staged-approval flow" is replaced by "Two ways a refresh is
refused", because the set brings back the hazard a single file did not have. A
corrupt source fails the parse loudly; a source that has VANISHED parses
perfectly and imports a table quietly missing everything it contributed. That
is the same ambiguity §6 stages a facet removal for, so it is staged here too
(`needsReview`, `{approve:true}` to accept) — as a flag rather than §6's
approve/reject pair, because the atlas stores a pending decision SO THAT
approving re-parses, and here nothing is stored.

Two more traps recorded in §8.6 (now five), both found by running a shard-style
overlay rather than another stock-table fixture:

- Tidying punctuation unconditionally corrupts real names — a custom
  "Runic Gateway Sigil (v2)" rendered as "(v2". Stripping leftover brackets is
  right after a placeholder is removed and wrong otherwise, the same condition
  the `%` rule already had.
- Source labels must be forward-slashed and root-relative or the same directory
  fingerprints differently on Windows and Linux and every boot looks like a
  change — the identical bug §6 records.

BACKEND_DESIGN.md: the source set, the two refusals, and the updated admin
route notes (`approve`, `missingSources`, overlays found beside a file path).

Documentation only. Every claim was written after the behaviour was observed:
the counts come from the live shard's script tree, and the refusal/approve flow
and per-source breakdown are copied from real runs in website #115.

PROJECT_TREE.md files are deliberately untouched — CI-generated by the
sync-project-tree workflow.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-29 06:46:43 -05:00
e3aabf9e3e docs(link): fill in the cliloc PR numbers, correct the cliloc file count
The progress table and CLILOCS.md carried #TBD placeholders until the PRs
existed; they now point at website #115 and docs #70.

Also corrects "all four Cliloc.* files" to eight (chs, cht, deu, enu, esp,
fra, jpn, kor) in both v3.md §8.6 and CLILOCS.md — the compression marker was
checked against every one of them, and understating the sample weakens the
claim it supports.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-29 04:25:06 -05:00
be9f5019fa docs(link): the cliloc table, and why §8.6's recommendation was not implementable
Protocol 3.0 §8.6 resolved as its own website-only change, landing ahead of
§8 so the marketplace ships with real item names. Matching documentation for
website #TBD.

NEW website/CLILOCS.md — operator-facing: why the conversion step exists, how
to convert, how to configure the path, the refresh contract, what gets stored
and how names are applied.

link/v3.md §8.6 rewritten. Two things in the original recommendation turned
out to be wrong, and both are recorded because the reasoning generalises:

1. The committed db/data/clilocs.json artifact predates the Part C
   corrections (§6.1) and violates both — no committed snapshot of derived
   content, and nothing EA-derived ever shipped. UO's strings are EA's,
   exactly as the creature sprites are.
2. "scripts/buildClilocs.js reads the UO client's Cliloc.enu" is not
   possible. EVERY current client ships its cliloc files compressed (first
   DWORD's high byte 0x8E, the Mythic container); the plain layout is what
   those files looked like before that change, and parsing one as the other
   does not fail cleanly — it yields ~19k records with negative ids, 1,722
   distinct keys out of 19,508, one 62 KB "string", and a truncation
   somewhere in the middle. ServUO's own Ultima.StringList cannot read it
   either, so VendorSearch.GetItemName is already inert on such a shard and
   the work could not be pushed to the plugin.

That second point also retires an open question in §8.2: the warning never to
call GetItemName in the market sweep costs us nothing we could otherwise have
had, because the in-game Vendor Search gump has the same gap.

Three traps found by building it are recorded: StringList.SaveStringList
RE-COMPRESSES on save (its output is byte-identical to its compressed input,
because its purpose is round-tripping a file back into the client); trimming a
text line before splitting silently drops the ~half of a table that is empty
strings; and Number('') is 0, not NaN.

Also updated:
- Progress and §9 sequencing tables: order 5 split into 5a (this, website
  only) and 5b (the four-repo wire change).
- website/BACKEND_DESIGN.md — shard_clilocs / shard_cliloc_meta, the three
  admin routes, and why there is no staged-approval flow and no public route.
- link/INTEGRATION.md — the char.profile field note now says explicitly not to
  expect the shard to resolve clilocs, and points at CLILOCS.md.
- §10 documentation obligations list CLILOCS.md.

Documentation only. Every claim was written after the corresponding behaviour
was observed running: the compressed-format finding and the parse failures
come from the real client files on this machine, and the counts (123,490
parsed → 67,496 stored) and timings from importing them into the live MariaDB.

PROJECT_TREE.md files are deliberately untouched — they are CI-generated by
the sync-project-tree workflow and say so in their header.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-29 04:22:05 -05:00
f715323aa0 Merge pull request 'docs(link): points.board, the leaderboards API, and what a real shard changed' (#69) from feat/points-board into edge
Reviewed-on: #69
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-07-29 07:52:25 +00:00
8e857a9c8d docs(link): points.board, the leaderboards API, and what a real shard changed
Protocol 3.0 §7 lands across servuo-plugins, link and website; this is the
matching documentation.

INTEGRATION.md
  - points.board in the event catalog: one frame per system, never a delta, no
    points.remove (the shard's system set is fixed at startup). Four gotchas
    called out, all of them things a consumer gets wrong by default:
      1. maxPoints: 0 means UNCAPPED, not "zero allowed" — and on a real shard
         it is the COMMON case, so anything rendering points/maxPoints must
         special-case it or divide by zero on the happy path.
      2. nameString is usually null, with a cliloc in nameNumber — so
         humanising the system key is the primary display path, not a fallback.
      3. players counts players actually holding points, not table size: ten of
         the ~25 systems keep a zero-point row per character ever created, so
         the raw count would report the shard's whole census.
      4. Entries carry serial + name only, never acct/webId.
  - The char.profile `points` block, and why `rank` is absent by default.
  - GET /points and /points/:system, including why 404 (never published) and
    200-with-empty-top (published, nobody scored) are different answers.

v3.md
  - B/2 marked done in both the progress table and §9.
  - NEW §7.5, "what the run against a real shard changed" — the same record §6.1
    and §6.2 keep. Four corrections the plan could not have anticipated from
    reading PointsSystem.cs, the sharpest being that (long)double.MaxValue is an
    unchecked conversion yielding long.MinValue, which published
    "maxPoints": -9223372036854775808 on the first live sweep. Also records that
    GetEntry/GetPoints mutate the world on AutoAdd systems and so cannot be used
    in a read model, and the one deliberate deviation from §7.4: the visibility
    field rule must key on the wire's `name`, not the descriptive
    `characterName`, or it is silently inert.

BACKEND_DESIGN.md — shard_points_boards (including why the top-N list stays in
the payload and why listing orders by COALESCE(name, system)), plus the two new
public routes.

PLAN.md — 3.0 phasing brought current: the spawn atlas and points.board added to
what has shipped, and the Points* keys noted in the config-key paragraph.

PROJECT_TREE.md files are deliberately untouched — they are CI-generated and say
so.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-28 21:05:04 -05:00
64fb7edc3e Merge pull request 'docs(website): the spawn atlas API, the admin panel, and the delay-unit trap' (#68) from docs/spawn-atlas-api into edge
Reviewed-on: #68
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-07-29 00:53:54 +00:00
be7e1a69ce docs(website): the spawn atlas API, the admin panel, and the delay-unit trap
Docs half of website #113 (Protocol 3.0 Part C, second website PR).

Carries the atlas rewrite that missed #67: that PR merged before the "derive
from the tree on every boot" commit was pushed, so `edge` currently describes
the build/import-artifact design that was rejected in review, not what shipped
in website #112. It lands here.

New in SPAWN_ATLAS.md: the six public routes and five admin ones, and three
behaviours that read as bugs unless they are written down — an unreadable tree
answers 200 with status "unavailable" rather than 500 (refresh reports outcomes
so boot is never blocked by a bad tree, and the contract is preserved at the
API), setting the ServUO path deliberately does not import, and `points` is a
count while `spawners` is the list.

Also the delay-unit trap: XmlSpawner stores MinDelay/MaxDelay in minutes OR
seconds per record, decided by that record's own DelayInSec flag, so a `5` is
five minutes on one spawner and five seconds on the next. Both are plausible
respawn times, which is what makes it silent. 170 of 6,455 stock spawners are
second-flagged. And PARSER_VERSION, which exists because hashing the tree alone
would strand an install whose maps never change on whatever an older parser
derived.

BACKEND_DESIGN.md gains the routes, the router-map entry, and the parser-version
rule. v3.md marks Part C done and records in 6.3 what the API half found.

api-route-inventory.json refreshed from the live manifest — it had drifted to
200 routes before this PR (real count was 204) and is now 215.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U7CBg11prhLimL9iHSX1bP
2026-07-28 19:51:50 -05:00
1b7da860b5 Merge remote-tracking branch 'origin/edge' into docs/spawn-atlas 2026-07-28 16:47:54 -05:00
ff1c2064a5 docs(website): the atlas reads the shard's tree on every boot, not a snapshot
Follows the redesign in website #112. Two decisions from the original §6 were
rejected in review and replaced; the docs now describe what was actually built.

**The committed artifact is gone.** A shard's maps change over its life, so a
snapshot in the repo silently drifts from the world players actually see. The
ServUO tree is the single source of truth and the atlas is re-derived on every
server boot, hash-gated so an unchanged tree costs one read pass and no write.

**Nothing may name a facet.** The first implementation carried a lookup table of
the six stock UO facets. A shard may add facets, replace them outright, or rename
them when its maps are updated, and a built-in list mishandles all three
silently. Reconciliation is now by matching against the facet set discovered from
the shard's own data, with an unmatched name keeping its own rather than being
forced into a wrong bucket.

## Changes

- **`website/SPAWN_ATLAS.md`** rewritten: the two ideas that shape the design,
  how to configure the tree path, the boot flow as a decision tree, the
  approve/reject flow, and the code layout. The artwork policy is unchanged and
  still explicit — no art ever ships, operators extract their own from their own
  client files.
- **`link/v3.md` §6.1 (new)** records the two rejected decisions plus the two
  boot-path contracts. The old "what real data changed" list becomes §6.2. §6's
  now-superseded passages — the artifact bullet, the payload budget, the operator
  re-run story — are marked rather than deleted, so the reasoning stays legible.
- **`website/BACKEND_DESIGN.md`** documents `shard_atlas_pending` and the two
  contracts that make it safe: a facet removal is staged for a human, and the
  boot refresh can never block startup.

The two contracts are the part worth reviewing. Losing a facet is
indistinguishable at boot from a half-copied or mid-update tree, so it is staged
rather than applied; and no failure mode of the atlas — missing path, unreadable
mount, malformed file, database error — is allowed to stop the site coming up.

---

- [x] AI-assisted: written with **Claude Code** (Claude Opus 5), reviewed before opening.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U7CBg11prhLimL9iHSX1bP
2026-07-28 16:44:39 -05:00
10ae129b94 Merge pull request 'docs(website): record the spawn atlas pipeline and what real data changed' (#67) from docs/spawn-atlas into edge
Reviewed-on: #67
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-07-28 21:16:06 +00:00
3fb3f63f25 docs(website): record the spawn atlas pipeline and what real data changed
Protocol 3.0 order 3 (Part C), docs half of website #112. Part C is website-only
— no plugin, no sidecar, no new kinds, no wire change.

## New: website/SPAWN_ATLAS.md

The operator-facing reference: the build/import split and why it exists (build
needs a ServUO tree, import does not, and the container has the artifact but not
the tree), the re-run story, the artifact format, the placement transform, and
the three quirks in the source data that are silent when unhandled.

Also documents the artwork policy explicitly: **the project ships no creature
art and no extraction tooling.** Sprites live in the operator's own client
.mul/.uop files and are theirs, not ours to redistribute. `art` is nullable and
NULL on every fresh import; an operator who wants art extracts it themselves into
the gitignored uploads/atlas/ and maps slugs in a gitignored art map. Text-only
is the normal, supported state — not a degraded one.

## New: v3.md §6.1 — what the build against real data changed

Six corrections, kept as a diff rather than edited into §6 in place, because
each is a trap the next person would otherwise re-enter:

1. **Six facets, not thirteen.** Eodon.xml and the other named-area files carry
   TerMur/Trammel points; the facet comes from each record's `<Map>`.
2. **The XML dependency call resolved: hand-rolled, zero deps.** §6 left
   fast-xml-parser vs a tokenizer open.
3. **Facet names disagree between sources** — Locations says `Ter Mur`, `<Map>`
   says `TerMur`. Unreconciled the landmark fallback never fires there and every
   unregioned Ter Mur/Tokuno spawn silently reads "Wilderness".
4. **Spawn type tokens carry XmlSpawner directives** (`Fairy,{RND,4,8}`,
   `alchemist/z/-50`). Taken literally they invent creatures that do not exist
   and split real ones in two. 71 of 845 affected; 800 remain after stripping.
5. **The artifact is 1.41 MB, not "well under 1 MB"** — down from 4.40 MB via
   three encodings. Getting under 1 MB would mean dropping the spawner name.
6. **DELETE, not TRUNCATE** — TRUNCATE is DDL in MariaDB and implicitly commits,
   which would defeat the all-or-nothing reload the design asked for.

§6 also now records that Part C ships as two website PRs: the parsing half is
where the correctness risk lives and should not be reviewed inside a 10k-line
diff alongside routes and React.

## BACKEND_DESIGN.md

The seven atlas tables, the import-owned contract, the four column choices that
are traps (`spawn_range`/`grp` reserved words, DELETE vs TRUNCATE, explicit point
ids, plain INDEX not FULLTEXT), and the distinction between the configured
champion roster and the live champ.update feed.

PROJECT_TREE.md is left alone — it is auto-generated by the sync-project-tree
workflow.

---

- [x] AI-assisted: written with **Claude Code** (Claude Opus 5), reviewed before opening.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U7CBg11prhLimL9iHSX1bP
2026-07-28 16:13:30 -05:00
e9ecdc0ecb Merge pull request 'docs(link): record world.ruleset and mark Protocol 3.0 progress' (#66) from docs/world-ruleset into edge
Reviewed-on: #66
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-07-28 20:34:38 +00:00
09467c67b0 docs(link): link the world.ruleset PRs from the v3 progress table
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-28 14:41:54 -05:00
b0a2207c6a docs(link): record world.ruleset and mark Protocol 3.0 progress
Protocol 3.0 order 2 (v3.md §5) is built across all four repos; this is its
documentation half, plus the running progress record the plan was missing.

v3.md
  - A progress table at the top and a State column on §9's sequencing table, so
    "what has landed" is answerable without reading four git logs. Part A (order
    1) and world.ruleset (order 2) are marked done; the spawn atlas is next.
  - §5 gains the implementation notes worth keeping, chiefly: where a system's
    on/off state is DERIVED rather than configured, read the system's own static
    instead of inventing a .cfg key (Shadowguard has no Enabled key — it's the
    TOL expansion gate; Factions is `!ViceVsVirtueSystem.Enabled` by
    construction in stock ServUO). Also that the plugin CAN be compile-verified
    despite the "no standalone build" caveat, and how.

INTEGRATION.md
  - The world.ruleset catalog entry and GET /ruleset, with the two things
    consumers get wrong: caps are in TENTHS (1000 = 100.0), and `connect` exists
    only if the operator set Bridge.PublicConnectAddress — the shard's real
    listen address is never published.
  - §2 now says plainly that v3 has NOT been bumped yet and what that means:
    sidecars on `edge` report 2 while already carrying some v3 kinds, so do not
    infer feature availability from the version during this window.

PROTOCOL_2.md §10.4
  - The deferred "which PvP system does this shard run?" is answered (VvV on,
    Factions off — and mutually exclusive by construction), and world.systems is
    marked superseded by world.ruleset, which carries the systems block it asked
    for. No orphan kind is left behind.

BACKEND_DESIGN.md — the shard_ruleset table (why it is stored whole rather than
normalized, and why no row means null rather than {}) and the public route.

PROJECT_TREE.md is deliberately untouched: sync-project-tree regenerates it on
push to main, so it updates itself at the v3 cutover.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-28 14:39:53 -05:00
bd9718a859 Merge pull request 'docs(shard): record the REST projection gap the Part A smoke test found' (#65) from docs/shard-visibility-rest-projection into edge
Reviewed-on: #65
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-07-28 15:56:20 +00:00
4c0ceb1c41 chore(docs): drop an unrelated working-tree file committed by mistake
android/TRUSTED_DEVICES_APP_HANDOFF.md was untracked in the working tree
before this branch and was swept in by a `git add -A`. It is not part of
this change; untracked here and left on disk.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-28 10:53:01 -05:00
35ad440bad docs(shard): record the REST projection gap the Part A smoke test found
The live five-rung smoke test of the visibility framework found that Part
A enforced it on the SSE path and on /guilds + /governors, but not on the
remaining public REST reads - so one event was projected live and served
verbatim from stored history.

link/v3.md gains 3.6.1 with the full list (the anonymous acct/webId leak
on /feed, the flattened ownerAcct on /idoc, the dead `houses` field
rules, /feed ignoring live config, the empty-allowlist fall-through, and
the Date-to-{} projection bug), plus the rule it leaves behind: a read
path that returns shard data and does not project is a bug, and every new
Part B/C surface must gate its kind set on live config rather than on
PUBLIC_KINDS.

3.5 also corrected: the table is NOT seeded on boot. An absent row means
"use the compiled default", which keeps the defaults in one place instead
of duplicating them into a seeder that could drift.

BACKEND_DESIGN.md 6.5 records the same as a security contract: rule 1
locks a field by meaning rather than spelling; PUBLIC_KINDS is a
module-load constant and must not answer per-caller questions;
projectFeature walks arrays and plain objects only.

SHARD_VISIBILITY.md gets the admin-facing version - that stored history
answers the same way the live stream does, and that turning live updates
off stops the push, not the reading.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-28 10:52:13 -05:00
5cb77595aa Merge pull request 'docs(website): record the shard visibility framework' (#64) from docs/shard-visibility into edge
Reviewed-on: #64
2026-07-28 15:06:56 +00:00
8b4fc439ee Merge branch 'edge' into docs/shard-visibility 2026-07-28 15:06:39 +00:00
bf41105ec0 docs(website): record the shard visibility framework
Protocol 3.0 Part A. Admin-configurable, per-feature and per-field
audience control over every shard-derived surface, replacing the static
PUBLIC_KINDS allowlist that used to be the whole boundary.

- SHARD_VISIBILITY.md (new): the admin-facing guide - the ladder, what
  each of the ten features exposes, the defaults, the two rules that are
  code rather than configuration, and worked examples.
- BACKEND_DESIGN.md 6.5 (new): the same thing as a security contract -
  the ladder and how viewerLevel resolves it, the locked acct/webId rule,
  the fail-closed kind map, the asymmetric ladder fallbacks, and the
  three enforcement points. Plus the shard_feature_visibility schema, the
  /public/shard/features route, and the adminOnly tier on
  /admin/shard/visibility.

Defaults reproduce pre-3.0 behavior everywhere, with one deliberate
exception which is the leak Part A was written to close: guilds and
governors previously returned the raw stored payload, whose leader and
governor actors carry acct and webId, to anonymous callers.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-28 10:04:29 -05:00
7e8cbe1916 Merge pull request 'docs(link): add the Protocol 3.0 design' (#63) from docs/link-v3-plan into edge
Reviewed-on: #63
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-07-28 14:41:26 +00:00
6622afe4bd docs(link): add the Protocol 3.0 design
Surveys the live ServUO tree against everything the bridge already
surfaces and records the full gap list (17 items), then specs the four
features scoped for 3.0.

3.0 has three scope areas:

- A: the visibility framework. Admin-configurable, per-feature and
  per-field audience control over all ten shard-derived surfaces (the
  four new ones plus the six that already ship), on an
  anonymous -> logged_in -> player -> staff -> admin ladder. Every
  default reproduces today's behavior, so the retrofit is a no-op until
  an admin changes something. Two rules an admin cannot override: acct
  and webId are admin-only always, and an unmapped event kind is never
  broadcast below admin. This also fixes a verified leak - guild leader
  acct/webId are readable today on the anonymous /public/shard/guilds.
- B: three new wire streams - world.ruleset, points.board, and
  vendor.listing/vendor.listing.remove.
- C: the spawn atlas, built from static ServUO data files with no wire
  involvement.

Visibility lives entirely on the website; the sidecar stays a dumb
forwarder that defines no access parameters and advertises no
capabilities.

PROTOCOL_VERSION goes 2 -> 3 once, at the end: every part PRs into an
edge branch per repo, and the coordinated edge -> main merge is the
cutover. A schema migration moves uo_link_config.protocol so operators
don't have to.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-28 09:28:04 -05:00
b523336313 Merge pull request 'docs(website): record trusted-device support on the SSO login paths' (#62) from feat/sso-trusted-device into main
Reviewed-on: #62
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-07-28 06:06:58 +00:00
e4bec0caba docs(website): record trusted-device support on the SSO login paths
Doc side of website + Android-app feat/sso-trusted-device.

TRUSTED_DEVICES_MFA.md §6 gains an "SSO login paths" subsection: SSO is not
exempt from the second factor, and a trusted device skips it exactly as on the
password path (previously SSO consulted trust nowhere, so an external-identity
user was asked for a code on every sign-in). Documents the callback-side skip,
the new trustDevice/deviceName on POST /auth/sso/totp, and why recovery codes
stay password-login only.

Also writes down how this reaches the Android app, since it is not obvious: the
app's SSO runs in a Custom Tab that shares the system browser's cookie jar, so
the rg_trust cookie covers native SSO with no app change and no trust token in a
start URL (which would leak a secret into query strings and logs). The app's own
token is minted at /auth/mobile/sso/exchange instead — an authenticated
app→server call — so it never travels in the deep link, and the bridge row holds
only a boolean. Notes that one tick yields two independently-revocable rows.

§4 documents the new mobile_auth_sessions.trust_device column; BACKEND_DESIGN.md
gets the same column in its bridge table, the trust note on the /exchange row,
and a pointer from the bridge intro to the Custom Tab cookie model.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-28 01:01:49 -05:00
b2c27fb285 Merge pull request 'docs(website): record the uo-link client config-decrypt contract and the dashboard mixed tier' (#61) from fix/uolink-client-contract-and-sitemode-gate into main
Reviewed-on: #61
2026-07-28 05:31:52 +00:00
2b93529ef6 docs(website): record the uo-link client's config-decrypt contract and the dashboard's mixed tier
Two corrections found by a live smoke test of all 200 routes at every access
level (website PR: fix/uolink-client-throw-and-sitemode-gate).

ARCHITECTURE.md: the "uoLinkClient never throws" invariant was true of the HTTP
call but not of resolving the config, which decrypts the stored auth token and
throws when the ciphertext can't be authenticated (SECRET_ENC_KEY rotated, or a
DB dump restored under a different key). Spell out that this is now handled
inside the client, reported as { ok: false, error: 'uo-link config unreadable' }
with a distinct ERROR log, and that GET /admin/uo-link/config keeps working —
it is the screen an admin needs to re-enter the token and recover.

BACKEND_DESIGN.md: GET /dashboard is staff-wide while PUT /site-mode on the
same screen is adminOnly — the one place a single screen spans two tiers. Note
that the client must gate that control itself rather than relying on the route
gate that admitted the user to the page.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-28 00:23:22 -05:00
9f3f014f34 Merge pull request 'docs(website): record PR 5 — public, player and auth capability split' (#60) from docs/router-split-5 into main
Reviewed-on: #60
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-07-28 01:56:22 +00:00
257ed2166c docs(website): record PR 5 — public, player and auth capability split
The domain split is complete. API_V2_PLAN.md gains a "PR 5 — as landed"
section (route table, the four zero-diff gates, and the findings worth
carrying forward) and its status line and sequencing list are updated: only
the CSP enforce PR remains, blocked on soak data rather than on code.

BACKEND_DESIGN.md §2 replaces the auth.routes.js / public.routes.js entries
with the full per-capability tree for auth/, public/ and player/, and §4's
group headings now point at the index.js files. The /player prose names the
three routers behind the shared gate.

Findings recorded rather than left in the code alone:

- public/ and auth/ deliberately have no group gate — the obvious hardening
  edit to either is an outage.
- GET /auth/me depends on session.router.js being mounted last, because
  use('/me', meRouter) matches the bare /me and supplies its noindex header.
- Two root-mounted routers (public/site, auth/session) on the PR 4 dashboard
  precedent, safe only because neither declares router-level middleware.
- loginGuards is the PR's shared module, the counterpart to PR 3's
  imageUpload.js.
- Filename deviations from the target tree (posts not news, session.router.js
  added) and why public.controller.js was not split.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-27 20:52:29 -05:00
bab70a3f6f Merge pull request 'docs(tree): sync website/PROJECT_TREE.md' (#57) from chore/sync-website-tree into main
Reviewed-on: #57
2026-07-28 01:32:42 +00:00
9c0c8f902c Merge branch 'main' into chore/sync-website-tree 2026-07-28 01:32:33 +00:00
a3e3ca817e Merge pull request 'docs(website): record the PR 4 admin router split and the end of admin.routes.js' (#58) from docs/admin-router-split-4 into main
Reviewed-on: #58
2026-07-28 01:31:55 +00:00
45fce7cb9f docs(website): record the PR 4 admin router split and the end of admin.routes.js
Covers website PR 4, the last admin split PR: shard (16), uo-link (5), email
(6), discord-bot (2), settings (2) and dashboard/site-mode (2) leave the
residual file, which is deleted. The admin group is fully split.

API_V2_PLAN.md gains a "PR 4 — as landed" section recording the two decisions a
reviewer would otherwise have to reconstruct: dashboard.router.js is mounted at
the group root (the single relaxation of the mount-at-a-prefix rule, safe only
because it declares no router-level middleware), and /shard keeps two gate tiers
in one router because prefix ownership beats swagger-tag grouping. Sequencing
item 7 is marked landed; PR 5 (public/player/auth) is the only split PR left.

BACKEND_DESIGN.md §2 gets the six new routers in the folder tree and drops the
residual entry; §4's /admin preamble now describes the ops/config gates instead
of pointing at a file that no longer exists.

WIKI_UPGRADE.md's two links into admin.routes.js are repointed at wiki.router.js
and admin/imageUpload.js.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-27 20:02:49 -05:00
runic-docs-bot
2efc32c022 docs(tree): sync website/PROJECT_TREE.md from RunicGateway/website@812b895 [skip ci] 2026-07-28 00:36:40 +00:00
e892d80aa8 Merge pull request 'docs(tree): sync website/PROJECT_TREE.md' (#55) from chore/sync-website-tree into main
Reviewed-on: #55
2026-07-28 00:36:08 +00:00
43d01fde03 Merge branch 'main' into chore/sync-website-tree 2026-07-28 00:35:57 +00:00
9f6ad6888d Merge pull request 'docs(website): record the PR 3 admin router split (posts, uploads, wiki, pages)' (#56) from docs/admin-router-split-3 into main
Reviewed-on: #56
2026-07-28 00:28:24 +00:00
d515d42b7c docs(website): record the PR 3 admin router split (posts, uploads, wiki, pages)
Adds a "PR 3 — as landed" section to API_V2_PLAN.md and ticks the sequencing
list. 31 routes extracted, 33 left in admin.routes.js; all four zero-diff gates
came back clean and 434 server tests passed.

Findings carried forward:

- The residual 33 is exactly PR 4's list, so admin.routes.js is deleted by
  PR 4 rather than PR 5.
- First shared module in the split: the multer config, because POST
  /posts/upload and POST /uploads no longer live in the same file.
- POST /uploads keeps its Admin · Posts swagger tag — retagging is a real
  OpenAPI diff and does not belong in a route-move PR.
- The wiki router has load-bearing intra-file route order (/categories and
  /tags ahead of /:slug) that no gate can catch, because the manifest sorts
  its entries. Verified by introspecting the built router stack instead.

BACKEND_DESIGN.md §2 gets the four new routers plus imageUpload.js in the
folder tree, and §4 notes that the content capabilities add no gate beyond
staffOnly.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-27 19:24:58 -05:00
runic-docs-bot
c47c89c023 docs(tree): sync website/PROJECT_TREE.md from RunicGateway/website@4938432 [skip ci] 2026-07-28 00:12:16 +00:00
d034c6f673 Merge pull request 'docs(website): record the PR 2 admin router split (moderation, bot-activity, activity)' (#54) from docs/admin-router-split-2 into main
Reviewed-on: #54
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
2026-07-28 00:11:13 +00:00
a4d03bd956 docs(website): record the PR 2 admin router split (moderation, bot-activity, activity)
Matches the code change in website: 18 more admin routes carved into
moderation.router.js (15), botActivity.router.js (2) and activity.router.js (1),
leaving 64 in the residual admin.routes.js.

API_V2_PLAN.md gains a "PR 2 — as landed" section recording the four zero-diff
gates and two decisions worth carrying into PRs 3-5:

  - /activity gets its own file rather than the target tree's plan to park it as
    a singleton inside dashboard.router.js — honouring the tree would have left
    one route in the residual file for two PRs, and it is a genuinely separate
    capability (the staff audit log, not the dashboard's stats overview and not
    the botScore middleware's ban state). PR 4 therefore mounts dashboard and
    site-mode only; the target tree is updated to match.
  - A gate moves to a router-level `use` only where it was already a *prefix*
    mount (moderation's modAccess). Bot-activity's per-route adminOnly stays
    per-route, because the per-route handler count is the only thing in
    routes.guards.json that would catch a dropped gate — requireRole(...) returns
    an anonymous arrow and never appears by name.

BACKEND_DESIGN.md §2 (folder structure) and §4 (the /admin contract preamble) are
updated for the new files and their gates. PROJECT_TREE.md is left alone — since
website#98 it is auto-generated by the sync-project-tree workflow.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-27 18:54:01 -05:00
7b699301e7 Merge pull request 'docs(tree): sync website/PROJECT_TREE.md' (#50) from chore/sync-website-tree into main
Reviewed-on: #50
2026-07-27 21:42:01 +00:00
runic-docs-bot
bd8adf1c54 docs(tree): sync website/PROJECT_TREE.md from RunicGateway/website@0e11e28 [skip ci] 2026-07-27 21:01:09 +00:00
fec3aa0d5d Merge pull request 'docs(website): record split PR 1 — admin users, account, invites, auth providers' (#53) from docs/admin-router-split-1 into main
Reviewed-on: #53
2026-07-27 20:59:05 +00:00
a7186e2fb9 Merge branch 'main' into docs/admin-router-split-1 2026-07-27 20:58:55 +00:00