docs(link): the panel, and the two deletions it could see and no test could (Phase 8)

v8.md section 12.2 records the admin surface as built: one page, Admin -> Client
Files, over the creature catalogue, item and land art, and the cliloc table --
one page because they come off one client install and change on one event. The
four decisions are written down, including the one that kept the phase to a
single repo: section 14's "activity view" is the last import's own summary
rather than a filtered feed, which would have needed a core activity filter, a
`ctx.activity.list` helper and a MODULE_API bump to re-read rows the panel can
keep itself.

The part worth reading twice is what the walk found. Two of the three defects
were DELETIONS, both predate phase 8, and neither was reachable by a test that
did not already suspect them:

  * the body import diffing its manifest against every family's rows, so 313
    item pictures were staged for deletion under a sentence that was wrong about
    what had happened;
  * an approved vanish unlinking the sprite and keeping the row, so the key came
    back for review forever and the count never moved.

The rule they share is now stated: after phase 5, `shard_assets` is three
collections in one table, and every read of it that feeds a decision has to say
which one it means. Two of the three that did not were deletions.

CLILOCS.md and SPAWN_ATLAS.md now name the button that exists (Admin -> Client
Files) instead of the one they promised, and modules/uo/API.md records that a
`needsReview` reply carries each vanished key's picture.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
This commit is contained in:
2026-09-14 08:10:49 -05:00
parent ae0efe26b3
commit fc2d4872f6
4 changed files with 83 additions and 11 deletions

View File

@@ -1492,6 +1492,67 @@ would put a second, weaker parser of §5's key scheme in the schema.
winning spelling of the spawn type token rather than inventing a display label — so the import has
something `ScriptCompiler.FindTypeByName` will resolve without storing it twice.
### 12.2 What phase 8 built, and the two deletions the panel exposed
The admin surface is one page, **Admin → Client Files**, covering all three things that come off the
operator's UO client: the creature catalogue, item and land art, and the cliloc table. One page
rather than three because they are one job — they live in the same client install, cross the same
bridge, and all change at the same moment, when the operator patches that client (org lead,
2026-09-14). The cliloc pair had had no UI at all since phase 2, which meant that on an install
where boot deliberately never calls the shard, the only way to import 67,496 names was `curl`.
Four decisions were put before any code:
| | Decided |
|---|---|
| Scope | **One page**, three sections, one nav row |
| The "activity view" (§14) | **The last import's own summary**, not a filtered feed. Rejected: a `prefix` filter on core's activity list plus a `ctx.activity.list` read helper — it would have made a `module-uo`-only phase a three-repo one with a `MODULE_API` bump, to re-read rows the panel can keep itself |
| A vanished key | **Held in the page, not a table** — see below |
| Import vs Update | **Two named buttons**, §6's own vocabulary, rather than one button and a `force` checkbox |
**The review is in-session by construction, and that is why it needs no table.** The atlas persists
its equivalent (`shard_atlas_pending`) because *boot* re-parses the tree and would otherwise
re-prompt on every restart forever. An asset import only ever happens because somebody pressed a
button on this page, so the review is already in front of the person who caused it; declining is
not a decision to remember, it is not pressing the other button. What the panel adds is the
**pictures**: `body/820/a23` names nothing a human recognises, and "is it right that these
disappear?" cannot be answered from a list of keys. So `needsReview` now returns
`[{ key, file }]` and the page renders each one.
`shard_asset_meta` gained a `last` block — what the import did, and who pressed it. It is written
**after** the rows commit, because the interesting half (how many atlas creatures resolved) does not
exist until another shard round trip has happened, and widening the rows-and-meta transaction to
cover a network call is how an import holds a write lock for the length of a timeout. It is
cosmetic, so a failure to write it is logged and swallowed rather than failing an import that has
already applied.
#### The two defects, and why only this phase could find them
Both are **deletions**, both predate phase 8, and both were invisible until a screen displayed the
numbers side by side on a stock rig.
**One: every item picture reported as vanished.** The import diffs what the site holds against the
manifest, and a manifest is of one family by construction (§14 — one reply, one catalogue id). The
held side read the **whole table**. That was correct when phase 3 wrote it and stopped being correct
in phase 5, which put `static` and `land` rows in the same table: the body manifest never mentions
item art, so on the walk's rig **313 item pictures were staged for deletion** with a sentence saying
the shard had stopped offering them. The gate hid it for a while — an Update whose client files are
unchanged never reads a manifest — so the state that exposes it is the ordinary one: import, let the
warm pass run for a day, then patch the client.
**Two: an approved vanish deleted the picture and kept the row.** `removeSprite` unlinked the file;
nothing deleted the row. So the catalogue still counted a portrait that was gone, the atlas
derivation could still point a creature page at a missing file, and the next forced import staged
the same key for review **again** — reporting "nothing was changed" about a file it had already
deleted. The removals now go into `saveAssets`'s own transaction, beside the rows and the meta.
The third finding is smaller and the same shape: `countAssets()` was whole-table too, so the panel's
first render of a working install announced a **1,408-row creature catalogue** (1,095 portraits plus
313 item pictures) on the one screen an operator checks to see whether the import worked.
**The rule worth keeping: after phase 5, `shard_assets` is three collections in one table, and every
read of it that feeds a decision has to say which.** Two of the three that did not were deletions.
---
## 13. Visibility
@@ -1590,11 +1651,18 @@ change every consumer *does* see, exactly as §7 intends.
command explicitly and has no generic `/assets/*` forwarder, so `link` is in the phase too. The
table now says so.
**Website admin** (`Admin → Shard`, admin-only): status, **Import**, **Update**, approve/reject for
a vanished key, and the existing path settings. Every action to the admin activity log, as
`shard.assets.*`. Phase 3 shipped the two that make it reachable — `GET /admin/shard/assets` and
`POST /admin/shard/assets/import`, mirroring the cliloc pair — so the phase could be accepted on a
real rig; the full panel is phase 8 (org lead, 2026-09-10).
**Website admin** `Admin → Client Files`, admin-only, **built in phase 8** (§12.2). Status,
**Update**, **Re-import everything**, approve/decline for a vanished key, the item-art warm pass and
the cliloc path setting, in three sections on one page. Every action goes to the admin activity log
as `shard.assets.*` / `shard.clilocs.*`, and the panel additionally keeps the **last import's own
summary** beside the catalogue, because that log is one unfiltered list of every admin action on the
site and an import from three client patches ago is not findable in it.
Phase 3 shipped the two routes that make it reachable — `GET /admin/shard/assets` and
`POST /admin/shard/assets/import`, mirroring the cliloc pair — so that phase could be accepted on a
real rig. The route surface did not grow in phase 8; what changed is that `needsReview` returns each
vanished key **with the picture the site currently serves for it**, and the status carries `linked`
and the `last` block.
---
@@ -1629,7 +1697,7 @@ disagree, so a split bump means the next bundle silently fails to compose.
| 5 | **DONE 2026-09-11.** Item statics and land on demand (§11.1): the `static` and `land` families, hue applied on the shard from `tiledata.mul`, the byte-bounded art cache, `assets.fetch` made family-aware, `families` on `assets.sources`. Website side: the warm pass, per-row `catalog` staleness, and pictures on the marketplace and the character sheet. **39,189 statics and 4,244 land tiles served; the only refusals are the 9,963 + 12,140 empty index slots §4.5 predicted.** Two traps found — the library's bitmap cache poisons a hued sprite, and `PartialHue` decides the picture from a file only the shard has. Protocol stays 8; `EXTRACTOR_VERSION` stays 2 | servuo-plugins, module-uo |
| 6 | **DONE 2026-09-11, and not what this row said.** The measurement came first and changed the phase: a complete one-direction animation set is **174,453 frames / 281.5 MB**, not the ~119,000 estimated, and **the site displays still pictures** — so the deep keys and the bulk-fill switch were **not built** (§11.2, org lead 2026-09-11). What shipped is what the still-picture site was missing: the **73 bodies with no art at action 0 and real art deeper** (a horse at `body/820/a23`), the catalogue key carrying its action, the atlas join that reads it, and §4.10's per-body **action ceiling** — without which the fallback walk itself would serve **452 validated pictures of the next body**. Catalogue **1,022 → 1,095**; `EXTRACTOR_VERSION` 2 → 3; protocol stays 8 | servuo-plugins, module-uo |
| 7 | **DONE 2026-09-14.** The atlas over the sidecar (§10); shared-filesystem requirement retired. The measurement came first again and changed the shape: `tree/<label>` → bytes **cannot work**`Spawns/trammel.xml` is 4.03 MB against a 1 MiB line cap — so a file crosses as **512 KiB chunks, each gzipped**, which is §5's depth scheme paying for itself a second time (§10.1). It is a `tree` **family** on `assets.fetch` rather than §14's separate commands, with `assets.manifest` generalised to match and its **own consent, `Bridge.TreeEnabled`** (§10.2) — so `link` needed nothing for the third phase running. **141 files / 11.9 MB / 158 chunks / 3 pages / 1.33 MB on the wire / 512 ms**, and the atlas built over the bridge is identical to the one built off the disk. Two defects, each found by a different harness: an empty `catalog` refusing every fetch, and `GZipStream` emitting **nothing** for the two empty files stock ServUO ships (§10.3). `PARSER_VERSION` 4 → 5 for one canonical read order (§10.4); protocol stays 8; `EXTRACTOR_VERSION` stays 3 | servuo-plugins, module-uo |
| 8 | Admin surface, Import/Update, approve/reject, activity log | module-uo |
| 8 | **DONE 2026-09-14.** The admin surface (§12.2): `Admin → Client Files` — one page over all three planes, because they come off one client and change on one event. The cliloc pair had had no UI since phase 2, which on a bridge install meant `curl` was the only way to import 67,496 names. §14's "activity view" is the **last import's own summary** rather than a filtered feed, which kept the phase to one repo (org lead, 2026-09-14). The walk imported **1,095 portraits in 3.5 s, warmed 313 item pictures in 0.6 s and reloaded 67,496 cliloc rows in 1.7 s** against a real shard — and found **two deletions nobody could see before a screen put the numbers together**: the body import diffing its manifest against *every* family's rows, which staged all 313 item pictures for deletion, and an approved vanish that unlinked the sprite and kept the row, so the key came back for review forever. Both fixed here; `EXTRACTOR_VERSION` and the protocol are untouched | module-uo |
| 9 | Docs pass across five repos; live walk on the real rig | docs |
Phase 0 exists because §4 chose to call code that can take the shard down if it is wrong, and the