PLAN_REDESIGNS §11.4 (D202-D208, D386-D390), protocol 14. Spec: docs
rust-link/PROTOCOL.md §20.12.
- RunicGatewayRaids.cs, the raids helper (D168, D204-D207, D387), shipped
beside the bridge and released with it. Harmony patches on RaidableBases'
spawn queue Add, OpenEvent and each base's BypassUseOwners(): a run's base
is tagged at birth, its PvP/NPCs/despawn/lock set on a member-by-member copy
of its profile (Setup, NPC and DespawnOptions copied again, so the shared
profile is never touched), PvP also set on the base after OpenEvent; a spot
checked (own look, then the plugin's area check with its reason, rocks and
cliffs a warning; then the ocean level, D388, D389) and pasted; one base
despawned; profiles reloaded under watch. Both editions, every member found
by name. rgr.status.
- Hello's integrations.raidableBases: edition, the paid spawn call (looked
for by name, D348), CopyPaste, the plugin's own difficulties (D386), the
helper, and with it the grid and the profiles.
- raid.started / raid.completed / raid.ended / raid.purchased / raid.locked
from the lifecycle hooks; the unload trap's completions (no owner, no
raiders) are neither sent nor counted. The tally's raidsCompleted (Warlord).
- map.live's raids layer, kept by the hooks and seeded from GetAllEvents().
- raid.spawn: with the helper the base is a world registry entry, so
world.revert / world.owned / world.expired serve it (never-started after
15 min, raidablebases-unloaded); without it the paid call or rbevent, not
the run's. raid.check for the editor's advice (D208, D391).
- RaidableBases' profiles in the configuration editor (D205): a virtual
@data/RaidableBases/Profiles/ path, all-profiles-or-none writes, reloaded by
the helper and rolled back when a profile does not load.
Compiled on both rigs (Rust 25902425, Oxide 2.0.7830, Carbon 2.0.262) and
driven on both rigs with the paid 3.2.2 and the tier-3 pack through a
throwaway plugin: the pick and spot routes with every setting, a repeat key,
revert, expiry, and a good and a broken profile edit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Core records nothing for a step that failed or whose answer was lost, and
then reverts it with only the key: no server, no teardown. So a run request
now carries its `teardown`, stored in the key's record and never run with
it, and `{ key, teardownOf }` runs the teardown stored under `teardownOf`,
under its own key, at most once:
- only when something there may have run (some commands ran, or a reload
cut the run off); otherwise answered with nothing run and nothing kept;
- `busy` while the run it is for is still going;
- a reward carries no teardown (refused `malformed`), and a teardown takes
no placeholders.
Re-compiled and driven on both rigs (a failed run's teardown ran once, a
repeat ran nothing, nothing-ran and unknown keys ran nothing); rigs
restored to the protocol-13 bridge.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
A `command.run` frame (protocol 14, PROTOCOL.md §20.11) runs the console
commands a step's author typed: once, or once per online recipient with
{steamid} and {name} filled in (the name quoted the way the game's own
BuildCommand quotes an argument). D382-D384: no allowlist; offline
recipients are skipped and named.
- At most once per key: the key is written to commands-run.json before
anything runs; a repeat answers from the record, a repeat while running
joins the running job, a record a reload interrupted answers
`interrupted`. Kept 7 days, at most 500 records.
- Spread over frames: whole recipients per frame, up to 30 commands or 8 ms.
- unknown-command: ConsoleSystem.Run returns null both for an unknown command
and for one that replies nothing, so the run listens to Unity's log for the
game's own "Command '...' not found" line while each command runs (spiked
on Oxide 2.0.7830 and Carbon 2.0.262, Rust 25902425). The same capture is
the reply when Run returns nothing.
- rg.world shows commandKeys and commandsRunning.
Compiled on both rigs and driven through a throwaway plugin (repeat key,
unknown command, offline recipients, malformed requests); rigs restored to
the protocol-13 bridge.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Stage 7 of the §14.3 integration plan (D374): hello's integrations gains
zLevels { loaded, version }, sent again when ZLevels loads or unloads,
and rg.world prints zLevels=. No level is read or sent; its XP boosts
are permissions a run already lends.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Stage 6 of the §14.3 integration plan. Spec: docs rust-link/PROTOCOL.md §20.9.
- gc.started, gc.ended (the clearer, or nobody) and gc.failed from Guarded
Crate's three hooks; the start's key joins exactly, inside the call.
- gc.place { key, event, location as a world call } runs `<command> position
<event> x,y,z`; refuses npcspawn-missing itself, since Guarded Crate's
`position` does not check and the crate would land unguarded (D364).
- The running set is checked against Guarded Crate's private list (by
reflection): the hotload seed, `stop all` (no hook), and which event ended.
- Hello's integrations.guardedCrate with npcSpawn and the events from its data
file; the tally's guardedWon (Warden); map.live's guarded rows; rg.world.
- Guarded Crate's cargo plane no longer says airdrop.called.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Stage 5 of the §14.3 integration plan (docs rust-link/PROTOCOL.md §20.8).
- Three events from DT's own hooks: dt.started { x, z, key? },
dt.won { steamId, name, invaders (max 32), invaderCount, x, z } and
dt.ended { x, z }. None while the world is not live; a win still counts.
- The start hook fires inside the call that opens the chest, so the key a
dt.start is running joins its start exactly, not by a ten-second window.
- dt.start { key, level 0-2 } runs `dtevent <level>` through
ConsoleSystem.Run and answers dt.opened { key, level, repeat, x, z }. A key
already answered is answered again and opens nothing (kept seven days in
dt-starts.json). Refusals: malformed, bad-level, dt-missing,
console-unavailable (D363), dt-not-ready (nothing opened, DT's own "Manual
Event Failed" reply, under fifteen minutes since DT loaded, D365),
dt-no-spot (after), refused (anything else DT said).
- The tally gains treasuresWon (Treasure Hunter, D375); map.live's world gains
kind "treasure" for each running chest, seeded at load from DT's chests in
the world (DT has no API that lists them).
- Hello's integrations gain dangerousTreasures, sent again on DT's load and
unload. rg.world prints dangerousTreasures, treasures, dtStarts and
dtWarmingUp. README lists Dangerous Treasures.
checkPlugin: 81 hooks, every one void and listed; 18 tests. Compiled and
loaded on both rigs (Rust 25902425, Oxide 2.0.7830, Carbon 2.0.262);
rg.world read dangerousTreasures=False treasures=0, then both went back on
the protocol-13 bridge.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Stage 4 of the §14.3 integration plan, specified in docs
rust-link/PROTOCOL.md §20.7 and §17.3.
- pvp.zone.created { zoneId, event, x, z, radius, shape, durationSec } from
OnCreatedDynamicPVP, and pvp.zone.deleted { zoneId, event } from
OnDeletedDynamicPVP. Neither is sent while the world is not live.
- Size and place come from ZoneManager: a sphere's Radius, or half a cube's
longer side (cargo's zone is a cube); the position is the zone's transform,
so cargo's moves with the ship.
- map.live gains a `pvp` layer of the live zones, kept by the two hooks and
seeded from Dynamic PVP's own AllDynamicPVPZones / GetEventName at world
ready and when it loads; a zone ZoneManager no longer holds is dropped.
- hello's integrations.dynamicPvp, sent again when it loads or unloads;
rg.world prints dynamicPvp= and pvpZones=; both hooks join rg.hooks.
- The bridge never makes, maps or erases one of Dynamic PVP's zones.
Compiled and loaded on both rigs (Rust 25902425, Oxide 2.0.7830, Carbon
2.0.262).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Stage 3 of the §14.3 integration plan (D362, D368), specified in docs
rust-link/PROTOCOL.md §20.6.
- world.zone's optional truePve: {"exclude": true} or {"ruleset": "name"},
exactly one, the name 1-64 characters. Malformed is `malformed`; TruePVE
not loaded is `truepve-missing`.
- All or nothing: the zone (and its dome) is made, then AddOrUpdateMapping.
A false erases both and answers `truepve-ruleset` with the name (or
`refused` for exclude).
- The registry entry remembers its mapping, and every path that erases one
of our zones calls RemoveMapping first: the deadline, world.revert, a zone
world.owned finds gone, a new wipe. While TruePVE is away the id waits in
data/RunicGateway/truepve.json, flushed at OnPluginLoaded(TruePVE) and at
the bridge's own load.
- hello's integrations.truePve { loaded, version }, sent again when TruePVE
loads or unloads. rg.world prints truePve= and truePveLeftover=.
Compiled and loaded on both rigs (Rust 25902425, Oxide 2.0.7830, Carbon
2.0.262, TruePVE 2.4.6).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Stage 3 of the §14.3 integration plan (D362, D368): world.zone's optional
truePve ({exclude: true} or {ruleset}), all or nothing, unmapped on every
path that erases one of our zones, with truepve.json for erases while
TruePVE is away. Docs and PRs still to come.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Stage 1 of the §14.3 integration plan: the patrol helicopter, Bradley, the
cargo ship, the Chinook, airdrops, locked crates, the Travelling Vendor, the
satellite and the Deep Sea, with no plugin (D349).
- `world.event`, one frame kind whose `event` names which of 24 types
(PROTOCOL.md §20.5). The type is in `event`, never `type`: `type` is the
envelope's, and the rig's sidecar dropped every world event as untyped when
the plan's example put it there. Nothing is said before the world loads or
while the server quits. Positions are x/z; the site works out the grid.
- Credit (D372, D379): a damage table for the patrol helicopter, the Chinook
and Bradley; `heli.downed`, `chinook.downed` and `bradley.destroyed` carry
every candidate (blow, last, most, damagers) and the plugin credits nobody.
D174's `CreditVehicle` and the tally's apcKills/heliKills are gone.
- Locked crates told positively (oil rigs, cargo, ghost ship, a Chinook drop
joined at the crate's landing); anything else says nothing. A Bradley's
`where` comes from the game's spawners, not `RoadSpawned` (which defaults to
true). `airdrop.called` comes from the cargo plane's spawn: `supply.call`
raised neither OnAirdrop nor OnCargoPlaneSignaled.
- The tally gains cratesHacked and dropsLooted (Safecracker, Scavenger, D356).
- `world.call` (D354, D367, D378): one of a schedule's TriggeredEvents at
random, without Trigger (which would reset its clock); satellite.random for
the satellite. A position for the airdrop (InitDropPosition) and the patrol
helicopter (SetInitialDestination) only, both set before the spawn, as the
stage-1 spike measured (0-1 m and 10-54 m). Kept; the key is remembered seven
days in world-calls.json; EventsMaxWorldCalls (default and ceiling 10) per run.
- Hello/status `console.plugins` (D363): the bridge runs its own rg.probe
through ConsoleSystem.Run from a timer. Carbon 2.0.262 refuses plugin
commands only inside a console command (or a NextTick one queued), which is
how §14.2's spike tested; from a timer, and from the link reader's NextTick,
it finds them. Reads true on both rigs.
- checkPlugin.js: the probe must name a [ConsoleCommand] the plugin declares.
Compiled and loaded on both rigs (Rust 25902425, Oxide 2.0.7830, Carbon
2.0.262); heli.arrived, cargo.arrived and airdrop.called read back from each
rig's sidecar store. Rigs restored. 18 check tests pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
WhiteThunder's Backpacks as an optional integration (D199), as §11.3.2 designs it.
Read, never write (R2):
- hello/status `integrations.backpacks` adds `sizes`: the N of every registered
backpacks.size.N, ascending (dynamic-size profiles left out). Hello is resent
when Backpacks loads or unloads.
- `backpacks.read {steamId}`: API_ReadBackpackContentsAsJson, and only when that
answered a backpack, API_GetBackpackCapacityById — the backpack is loaded by
then, so the capacity follows a later grant (§11.3.1's trap). Each stack is
reduced to slot, shortname, display (the game's English name, D340), amount,
skin and name when set; a blueprint carries its target as `blueprint` (D341);
contents one level deep; `overflow` past the capacity. `pageSize` is
Backpacks' "Max size per page" from its config, cached by mtime.
- Refusals: backpacks-missing, bad-id, unreadable.
The bigger backpack an event lends is a backpacks.size.N permission pushed by
perm.sync (D337), so it needs no code here. Still protocol 14 (D332).
Compiled on both rigs (Rust changeset 167584; Oxide 2.0.7826, Carbon 2.0.262),
then restored to the bridge they ran before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Wulf's Economics as an optional integration (D199), as §11.2.2 designs it:
- hello/status `integrations.economics` adds startingBalance and wipesWithMap,
read from Economics.json (cached by mtime) because its API has neither.
- `economics.pay`: coins from an event step, kept once paid (D335). One main-
thread call for the list; each deposit measured as after - before, since
Deposit answers true while rounding and clamping (`at-limit` otherwise).
The key and its reply are kept in data/RunicGateway/payments.json for seven
days, so a retry answers `repeat: true` and pays nobody. The optional
message ({amount} = what landed) is told now if awake, else on wake.
- `economics.balances` (bounded by PermMaxOps) and staff `economics.adjust`
(deposit / withdraw / set; a withdrawal above the balance is refused
`insufficient` with the balance).
- `player.tally` gains `balance`, a value: read at connect (D336) and from
OnEconomicsBalanceUpdated, offline players included.
- `economics.wiped` from OnEconomicsDataWiped(IPlayer), with `newSave`.
Amounts are boxed as double and only Steam ids are sent, per the spike.
Compiled on both rigs (Oxide 2.0.7826, Carbon 2.0.262), then restored.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Opens protocol 14 (D332):
- kits.uses {kit, steamIds} -> {kit, max, cooldown, asked, exhausted,
onCooldown}: counts only, bounded by the sync's own bound (§2.6).
- perm.sync notices [{key, steamId, message, delivery}] (D333): the site's
whole untold set. Told at once to a player who is awake, else on
OnPlayerSleepEnded; popup where PopupNotifications is loaded and chosen,
chat otherwise. Told keys persist in notices.json until the site stops
sending them; the report lists noticesTold.
ProtocolVersion and overlay.toml 13 -> 14; OnPlayerSleepEnded joins
ExpectedHooks (checkPlugin: 55 hooks, all void).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
ZoneManager subscribes the hooks that enforce its flags only for the zones it
had when it loaded; a zone made later through its API, which is every zone an
event opens, never re-subscribes them, so an event's PvpGod or NoBuild zone
enforced nothing. The helper's postfix, which already sees every zone created,
updated or loaded, now has ZoneManager run its own UpdateHookSubscriptions on
the next tick, before entering the players already inside. A ZoneManager
without that method is warned about and reported by rgz.status; the first job
still works.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
- Animal kills: Rust's animals are now Rust.Ai.Gen2.BaseNPC2 (stag, boar, wolf,
bears, big cats, crocodile, farm animals, critters, sea life), not BaseNpc, so
no animal kill counted as an animal or an NPC kill. They count as both again;
ScientistNPC2, the one person among them, counts as a human NPC kill.
- Heals: OnHealingItemUse is (tool, fromPlayer, toTarget). The bridge took two
parameters, received the healer as the target, and dropped every heal as
healing yourself. It now reads the target, and one act of help (a syringe
revive raises both hooks, the heal first) counts once whichever hook comes
first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
world.place of one of Rust's own NPC prefabs now needs RunicNPC loaded and
answering RunicNpcApiNeeded, as a profile placement does, so a module that
missed the hello cannot place NPCs on a server the site calls incomplete. The
refusal comes before anything spawns, with the existing reasons
runicnpc-missing and runicnpc-old; a profile placement now also refuses an old
RunicNPC up front (it was refused only when it carried orders). Crates are
unaffected. No message changes shape; protocol stays 13.
overlay.toml's runicnpc_api comment and rg.npc's line say RunicNPC is required.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
One RunicNpc_List per map.live ask, joined to the ledger by net id
(D302, D303):
- A ledgered NPC's row gains its name and the step key that placed it.
A boss's row also gains `boss` and its `health` (0-1); the health is
for the event page's progress line.
- A RunicNPC NPC owned by a run that no step ledgered is a boss's add.
It joins the events layer under that run with `add: true`.
- Any other boss joins the world layer as `kind: "boss"` with its name.
- A placement's ordinary NPCs are not sent.
Protocol 13, still unreleased; the fields are additive.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
world.place with a profile takes dropLoot (D296): false spawns the
NPCs with RunicNPC's {"loot":{"dropTable":false}} override, so they
keep their corpse's start but roll no table. Absent or true, as before.
runicnpc_api and RunicNpcApiNeeded are 6.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
npc.boss.spawned, npc.boss.phase and npc.boss.died, from RunicNPC
API 5's boss hooks, for the site's spawned, phase and killed triggers
(D277). The death frame names the killer and counts the players who
hurt the boss; npc.died still carries every contributor.
A profile's `boss` and `use` blocks travel in the push unchanged.
runicnpc_api and RunicNpcApiNeeded are 5.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
- npc.profiles answers with RunicNPC's faction table, and npc.profiles.set
pushes one beside the profiles (RunicNpc_SetFactions), set first so the
profiles are checked against it; a refused table comes back as
factionsRefused and the old one is kept (D254);
- world.place with a profile takes escort (a Steam id; the player must be
on, else escort-offline), ally ({kind: clan|player, id}) and tether (the
run's own zone that holds the point, else no-zone), and gives each NPC
its orders through RunicNpc_Escort, _Ally and _Tether. All or nothing:
an order RunicNPC refuses removes every NPC the step placed (D269, D270,
D272);
- the RunicNPC API the bridge needs is 4 (overlay.toml runicnpc_api = 4).
Compiled on the Oxide rig.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
overlay.toml gains runicnpc_api = 3, the release folds it into manifest.json,
and checkPlugin.js holds it equal to RunicNpcApiNeeded in the code. The
installer's bundle carries the latest RunicNPC release that answers it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Protocol 13 gains npc.profiles, npc.profiles.set, npc.placements and
npc.placement (add, set, remove, rename, respawn), each calling RunicNPC's
API 3; the frames npc.died, npc.health and npc.placement.changed from its
hooks; runicNpc in the integrations body; world.place with a profile; the
killfeed's attackerNpc and attackerProfile; and the tally's
npcProfileKills. rg.npc reports it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
world.zone gains flags, settings, enterMessage, leaveMessage, delivery,
format and dome (PROTOCOL §19.11). Flags are checked against the live
ZoneManager's own list, read through its API call ZoneFieldListRaw() (D211),
and refused bad-option rather than dropped; settings are an allowlist of
five with bounds. Both are kept on the registry entry and re-applied when
a zone is re-created.
The bridge says a zone's enter and leave messages itself (D193), as chat
or a popup (chat when PopupNotifications is missing), and stays quiet for
5 s after re-creating a zone.
RunicGatewayDomes.cs, a new helper, patches ZoneDomes so its API can be
called without a player, and raises OnRgDomesReady after ZoneDomes' own
start; the bridge puts its domes back only after it, so none is drawn
twice. Domes are removed before their zone at expiry and revert.
hello carries zoneManager { version, flags }, integrations.zoneDomes the
ZoneDomes and helper state, and hello is sent again when ZoneManager,
ZoneDomes or either helper loads or unloads. The release ships the new
helper beside the zone helper.
Not compiled or walked on a rig: the panel's file API was down on both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
Compiled on both rigs (2026-09-29): ItemCraftTask has no owner, so the crafter is ItemCrafter.owner; OnMissionSucceeded credits its third argument, the assignee, since MissionInstance has no accessor for it; OnHealingItemUse takes its target as object, because GiveEffectsTo now takes an IMedicalToolTarget, and counts only a player. GameObject.ToBaseEntity is not in scope for a plugin, so OnEntityBuilt asks GetComponent<BuildingBlock>().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
player.tally gains animalKills, weaponKills (by the killfeed's weapon
prefab name), headshots, bestPvpM/bestPveM (this interval's maximum),
apcKills, heliKills, crafted (by item category), built, repaired, healed,
rockets, explosives and missions. Every field is absent when nothing moved.
Ten new void hooks, all in ExpectedHooks: OnEntityTakeDamage (the last
player to hurt an APC or helicopter, for a crash that names nobody),
OnItemCraftFinished, OnEntityBuilt, OnStructureRepair (counted only when
the health rose by the next tick), OnHealingItemUse and OnPlayerRevive
(another player only; a syringe revive counts once), OnRocketLaunched,
OnExplosiveThrown/Dropped (a damaging TimedExplosive only) and
OnMissionSucceeded.
npcKills keeps counting animals too, so the public column does not change
meaning mid-wipe; the site takes human NPCs as the difference (D209).
WeaponOf and DistanceOf are split out of DescribeAttacker so the killfeed
and the tally read one name and one distance.
Not yet compiled on a rig: the rigs' storage is unmounted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
The permission manager (PLAN_REDESIGNS section 1, D160): the site owns every
permission and group on the server, so the bridge now reads all of it.
- perm.inventory: every registered permission with the plugin that
registered it (PermissionExists(name, plugin), public on both
frameworks), every group with title, rank, parent and permissions,
every holder's direct permissions and groups (not default), and the
pairs event leases hold. Built a slice per tick, sent in pages under
the 1 MiB line cap, paged from one snapshot. rg.inventory logs it.
- perm.sync sets an existing group's title and rank and every group's
parent; an absent title or rank means "leave it" (the adopt policy's
hold). The managed list and the foreign scan are gone: the site reads
the store and decides.
Walked on both rigs: Oxide 85 permissions all owned, Carbon 104 with its
30 adminmodule.* unowned; ZoneManager owns ignoreflag.nokits.
Refs RunicGateway/Module-Rust#21
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY