docs(website): the server half does not slice, and why

Found before writing any of it. §2.7.1 split the server extraction five ways by
feature; it does not divide, because two contract rules compose.

A mount prefix is claimed whole -- ownedByCore probes the live tier router and
registerRoutes validates single-segment prefixes -- and /admin/shard is one
386-line router carrying 25 routes across atlas, clilocs, shard-ops, visibility,
market and account links. Meanwhile a model cannot be shared across the boundary
(§5.1), so it moves with the last route that consumes it. Take the closure and
every prefix is in it: /public/atlas holds shardAtlas with /admin/shard, which
holds shardState/shardEvents/shardMarket with /public/shard, which holds
uoLinkConfig with /admin/uo-link, and shardClilocs/shardLinks with /player/shard.

Landing any one of the old slices alone would either strand core importing
modules/uo/ -- what acceptance criterion 2 forbids -- or delete routes core is
still serving.

Giving the admin routes their own prefixes would divide it and is rejected: it
changes API URLs, which §1.2 promises not to do, and not hypothetically. The
shipped Android app calls POST /api/v1/admin/shard/kick, /ban, /unban,
/broadcast and the three /pages routes.

So the table is now six slices: 0 the skeleton (done), 1 the whole server half
as ONE PR per repo structured as five commits along the old slice lines, 2-4 the
client halves, 5 close the phase. The client half is unaffected and still slices
cleanly -- the registry takes routes per area, with no prefix atomicity and no
shared models, the same asymmetry that let the two halves be separated at all.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-11 01:50:37 -05:00
committed by Claude
parent 7548c20820
commit f402395fa5
2 changed files with 51 additions and 13 deletions

View File

@@ -777,21 +777,59 @@ Each slice is one `module-uo` PR (adds), one `website` PR (deletes), and one `do
| # | Slice | Moves |
| --- | --- | --- |
| 0 | **The bundle skeleton** | `module.json`, both `package.json`s, `server/index.js` registering nothing, the Vite library build + the four shared-dep shims, an empty `schema.sql`/`purge.sql`, CI armed, the §5.1 zero-internal-imports check. `website` untouched. |
| 1 | **Atlas + clilocs** | `shardAtlas`/`shardClilocs` models, `clilocParse`/`clilocSource`/`spawnAtlasParse`/`spawnAtlasSource`, `public/atlas.*`, `admin/shardAtlas.controller`/`shardClilocs.controller`, `scripts/importSpawnAtlas.js`, the art JSON |
| 2 | **The live shard** | `shardState`/`shardEvents`/`shardVisibility`/`uoLinkConfig` models, `uoLinkClient`/`uoLinkSocket`/`shardIngest`/`shardBroadcast`/`shardPush`/`shardVisibility` utils, `config/shardStreams.js`, `public/shard.*`, `admin/shard.router`/`shardOps`/`shardVisibility`/`uoLink.*` |
| 3 | **Market** | `shardMarket` model, `shardSales` util and their routes |
| 4 | **Account links + the extension slot** | `shardLinks` model, `player/shard.*`, `admin/usersShard.*` minus `getUser` — the first real filling of `admin.users.detail` |
| 5 | **The news leg** | `newsGump.js`, `shardAnnounce.js`, the town-crier `registerAnnounceLeg` |
| 6 | **Public pages** | `Shard`, `ShardActivity`, `Rules`, `Atlas`, `AtlasCreature`, `ChampSpawns`, `Market`, `MarketVendor`, `Governors`, `Guilds`, `Houses`, `Leaderboards`, `PlayersOnline`, `VendorSales`, `data/cityCrests.js`, `lib/shardEvents.js`, `lib/useShardFeed.js`, and their public nav rows — under `/uo/*` per §2.8 |
| 7 | **Admin + player pages** | `ShardAdmin`, `ShardOps`, `ShardVisibility`, `SpawnAtlas`, `HousesAdmin`, `AdminCharacter(s)`, `PlayerCharacter(s)`, `GameAccounts`, `CharacterSheet`, `CharacterStats`, `ShardAccountActions`, `CreateGameAccountForm`, `useShardFeatures` and the `useShardFlags` feature provider — under `/admin/uo/*` and `/player/uo/*` |
| 8 | **De-UO core's copy** | `About`, `Screenshots`, `Website`, `SiteFooter`, `heroLayout`'s defaults, `api/client.js`'s `shard`/`atlas` namespaces, and the comments in `navOverrides.js` — plus the §5.2 CI grep that keeps them out |
| 9 | **Close the phase** | `module-uo`'s frozen route manifest and release workflow; `docs/modules/uo/` and `docs/modules/rust-dryrun.md` |
| 0 | **The bundle skeleton** | `module.json`, both `package.json`s, `server/index.js` registering nothing, the Vite library build + the four shared-dep shims, CI armed, the §5.1 zero-internal-imports check. `website` untouched. |
| 1 | **The whole server half** | 40 files / ~9,674 lines, 25 of core's 82 test files, 27 of its 68 tables — every UO model, util, router and controller, `config/shardStreams.js`, `scripts/importSpawnAtlas.js` and the art JSON. **One merge, five commits** (below). |
| 2 | **Public pages** | `Shard`, `ShardActivity`, `Rules`, `Atlas`, `AtlasCreature`, `ChampSpawns`, `Market`, `MarketVendor`, `Governors`, `Guilds`, `Houses`, `Leaderboards`, `PlayersOnline`, `VendorSales`, `data/cityCrests.js`, `lib/shardEvents.js`, `lib/useShardFeed.js`, and their public nav rows — under `/uo/*` per §2.8 |
| 3 | **Admin + player pages** | `ShardAdmin`, `ShardOps`, `ShardVisibility`, `SpawnAtlas`, `HousesAdmin`, `AdminCharacter(s)`, `PlayerCharacter(s)`, `GameAccounts`, `CharacterSheet`, `CharacterStats`, `ShardAccountActions`, `CreateGameAccountForm`, `useShardFeatures` and the `useShardFlags` feature provider — under `/admin/uo/*` and `/player/uo/*` |
| 4 | **De-UO core's copy** | `About`, `Screenshots`, `Website`, `SiteFooter`, `heroLayout`'s defaults, `api/client.js`'s `shard`/`atlas` namespaces, and the comments in `navOverrides.js` — plus the §5.2 CI grep that keeps them out |
| 5 | **Close the phase** | `module-uo`'s frozen route manifest and release workflow; `docs/modules/uo/` and `docs/modules/rust-dryrun.md` |
##### Why the server half cannot be sliced — found 2026-08-11, before writing any of it
The table above used to run to ten slices, with the server half split five ways by feature. It does
not divide, and the reason is that two contract rules compose:
- **A mount prefix is claimed whole.** `ownedByCore` probes the live tier router and `registerRoutes`
validates single-segment prefixes, so `/admin/shard` moves as one unit — and it is a single
386-line router carrying 25 routes that span atlas, clilocs, shard-ops, visibility, market *and*
account links.
- **A model cannot be shared across the boundary** (§5.1), so a model moves with the *last* route
that consumes it.
Take the closure and every prefix is in it:
```
/public/atlas ──shardAtlas── /admin/shard ──shardState,shardEvents,shardMarket── /public/shard
│ │
shardClilocs,shardLinks uoLinkConfig
│ │
/player/shard /admin/uo-link
```
Landing any one of the old slices alone would either strand core importing `modules/uo/` — which is
precisely what acceptance criterion 2 forbids — or delete routes core is still serving.
**Giving the admin routes their own prefixes would divide it, and is rejected.** `/admin/atlas` and
`/admin/clilocs` alongside a slimmer `/admin/shard` would make the closure fall apart. It also
changes API URLs, which §1.2 promises not to do — and not hypothetically: the shipped Android app
calls `POST /api/v1/admin/shard/kick`, `/ban`, `/unban`, `/broadcast` and the three `/pages` routes
(`data/api/AdminApi.kt`). A prefix rename is a client break, and the API surface is frozen for
exactly this reason.
**So slice 1 is one PR per repo, structured as five commits** along the old slice lines, reviewable
one at a time while landing atomically: atlas + clilocs · the live shard · market · account links and
the `admin.users.detail` slot · the town-crier leg. The alternative considered was stacked PRs into a
per-repo integration branch; it buys PR-level granularity for ten extra PRs and two long-lived
branches, and commits give most of the same reading order for none of it.
**The client half is unaffected** and still slices cleanly: the client registry takes routes per
*area*, with no prefix atomicity and no shared models — which is the same asymmetry that let the two
halves be separated in the first place.
**Criterion 1 is a grep over code, not over prose** — see [API §5.2](MODULE_API.md#52-zero-uo-identifiers-in-core-ci-website-repo)
for what that means precisely. Core's marketing copy says "shard" in a dozen places, and a literal
word grep would have made every one of them a CI failure while proving nothing about the boundary.
Slice 8 rewrites that copy anyway, because a core that still reads as a UO site is not the
Slice 4 rewrites that copy anyway, because a core that still reads as a UO site is not the
game-agnostic platform this workstream is for — but it is a deliberate piece of work with its own
review, not an exemption hidden in a grep pattern.
@@ -827,7 +865,7 @@ of what to catch, and the entry point's comment explaining why a module must nev
`require('express')`. A check that cannot survive being described is one people stop writing
comments around, so it strips comments and template literals with a character walk rather than a
regexp (a URL in a string contains a comment opener; a comment contains quotes) and carries its own
test suite. The same applies to slice 8's §5.2 grep, which will be read by a codebase that discusses
test suite. The same applies to slice 4's §5.2 grep, which will be read by a codebase that discusses
modules constantly.
**One thing to know before running a module locally: the loader skips a *symlinked* module directory