feat(rust): notifications and engagement (phase 10, protocol 7) #13

Merged
whitlocktech merged 2 commits from feat/phase-10-engagement into edge 2026-09-23 18:44:00 +00:00
Member

What & why

Phase 10 (R7): the engagement set, as decided in docs/modules/rust/PLAN.md §25 (D59–D68). Pairs with Rust-Plugins and Rust-Link feat/phase-10-engagement (protocol 7) and docs docs/rust-phase-10.

  • 13 triggers, 4 push streams, 3 audiences, 4 bodies, and 13 rules in 7 groups, all disabled (server/engagement/).
  • The raid alert (rust.base.destroyed, ceiling owner) goes to everyone authorised on the tool cupboard: one emit per linked person, with ownerUserId set (D59).
    • It covers doors, walls and the cupboard (D60).
    • It carries ownerOnline. The seeded rule's condition is what makes it "offline only" (D61).
    • It never names the raider (D66), and nobody is alerted when there is no cupboard (D67).
  • The fan-out runs off ingest, before a frame is applied. A replayed event is told only while it's news: 15 minutes for broadcasts, 24 hours for personal and staff events (D63). Dedupe keys come from the event itself.
  • Transitions are tracked in memory: server online/offline and a new kills leader. A first sighting never counts, and a tie is not a lead. A login with no approval within a minute becomes a staff notice (via a query).
  • D68 (a phase-4 gap): the refresh now asks /health. A hung game, or an unloaded bridge, with the sidecar still up no longer reads as online, names nobody as online, and doesn't move "last seen".
  • Every trigger carries its own title/intro, so core's generic bodies say which server, clan or player (found by the live walk).
  • engagement-triggers.json is the committed freeze, checked in CI.

No announce leg and no post hook (D62 amends R7).

How it was tested

  • npm test in server/: 234 pass. Client: 41 pass. check:imports, check:bundle, check:swagger and check:engagement all pass. The new check was broken both ways to prove it's live: a changed label fails, CRLF-only passes.
  • Live walk against real core main on a fresh database, the protocol-7 sidecar inside the Oxide rig, and a rig-only helper that runs the real OnEntityDeath path:
    • Criterion: two authorised owners alerted once each. The bystander, the raider and an unlinked owner were told nothing.
    • Other raid cases: self-demolish → no alert; cupboard destroyed → alerted; no cupboard → no alert.
    • D68: a plugin unload was read as offline, and the online notice on reload cancelled the delayed offline notice.
  • Carbon: the same plugin loaded. Details in PLAN.md §25.5–25.7.

Checklist

  • I have read CONTRIBUTING.md.
  • The change builds and existing tests/checks pass locally.
  • I have added or updated tests/docs where it makes sense.
  • My commits are reasonably scoped with clear messages.

AI-assisted contributions (required)

  • AI tools were used. Tool(s): Claude Code (Claude Opus 5.5). AI-authored commits carry a Co-Authored-By trailer.

License

  • I agree that my contribution is licensed under this project's license (GNU GPL v3.0 or later).

🤖 Generated with Claude Code

https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY

## What & why Phase 10 (R7): the engagement set, as decided in `docs/modules/rust/PLAN.md` §25 (D59–D68). Pairs with Rust-Plugins and Rust-Link `feat/phase-10-engagement` (protocol 7) and docs `docs/rust-phase-10`. - **13 triggers, 4 push streams, 3 audiences, 4 bodies, and 13 rules in 7 groups, all disabled** (`server/engagement/`). - **The raid alert** (`rust.base.destroyed`, ceiling `owner`) goes to everyone authorised on the tool cupboard: one emit per linked person, with `ownerUserId` set (D59). - It covers doors, walls and the cupboard (D60). - It carries `ownerOnline`. The seeded rule's condition is what makes it "offline only" (D61). - It never names the raider (D66), and nobody is alerted when there is no cupboard (D67). - **The fan-out runs off ingest**, before a frame is applied. A replayed event is told only while it's news: 15 minutes for broadcasts, 24 hours for personal and staff events (D63). Dedupe keys come from the event itself. - **Transitions are tracked in memory:** server online/offline and a new kills leader. A first sighting never counts, and a tie is not a lead. A login with no approval within a minute becomes a staff notice (via a query). - **D68 (a phase-4 gap):** the refresh now asks `/health`. A hung game, or an unloaded bridge, with the sidecar still up no longer reads as online, names nobody as online, and doesn't move "last seen". - **Every trigger carries its own `title`/`intro`**, so core's generic bodies say *which* server, clan or player (found by the live walk). - **`engagement-triggers.json`** is the committed freeze, checked in CI. No announce leg and no post hook (D62 amends R7). ## How it was tested - `npm test` in `server/`: 234 pass. Client: 41 pass. `check:imports`, `check:bundle`, `check:swagger` and `check:engagement` all pass. The new check was broken both ways to prove it's live: a changed label fails, CRLF-only passes. - **Live walk** against real core `main` on a fresh database, the protocol-7 sidecar inside the Oxide rig, and a rig-only helper that runs the real `OnEntityDeath` path: - **Criterion:** two authorised owners alerted once each. The bystander, the raider and an unlinked owner were told nothing. - **Other raid cases:** self-demolish → no alert; cupboard destroyed → alerted; no cupboard → no alert. - **D68:** a plugin unload was read as offline, and the online notice on reload cancelled the delayed offline notice. - Carbon: the same plugin loaded. Details in PLAN.md §25.5–25.7. ## Checklist - [x] I have read [CONTRIBUTING.md](CONTRIBUTING.md). - [x] The change builds and existing tests/checks pass locally. - [x] I have added or updated tests/docs where it makes sense. - [x] My commits are reasonably scoped with clear messages. ## AI-assisted contributions (required) - [x] AI tools were used. Tool(s): `Claude Code (Claude Opus 5.5)`. AI-authored commits carry a `Co-Authored-By` trailer. ## License - [x] I agree that my contribution is licensed under this project's license (**GNU GPL v3.0 or later**). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
wtclaude added 2 commits 2026-09-23 18:42:11 +00:00
Registers the engagement set R7 put in v1: thirteen triggers, four push
streams, three audiences, four bodies (two triggers, email and in-app)
and thirteen disabled rules in seven groups (PLAN.md §25, D59-D68).

The raid alert goes to everyone authorised on the tool cupboard, one
emit per linked person with ownerUserId, so the owner ceiling holds per
emit. It covers doors and walls (protocol 7), never names the raider,
alerts nobody when there is no cupboard, and carries ownerOnline so
"offline only" is the seeded rule's condition rather than code.

The fan-out runs off ingest before a frame is applied, since applying a
disband deletes the roster the notice is sent to. A replayed event is
told only while it is news: 15 minutes for broadcasts, 24 hours for
personal and staff events. Dedupe keys come from the event, not the
sidecar's row id. Server online/offline and a new kills leader are
in-memory transitions, never on first sight, and a tie is not a lead.
A login with no approval within a minute becomes a staff notice via a
query, so a restart loses nothing.

Also fixes a phase-4 gap (D68): the refresh now asks /health, so a game
that hung, or whose bridge was unloaded, while the sidecar stayed up no
longer reads as online. It stops naming players as online, and a stale
board no longer moves "last seen".

engagement-triggers.json is the committed freeze of all of it, checked
in CI with line endings normalised. The check was verified by breaking
it both ways.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
fix(rust): every notice says which server, clan or player it is about
All checks were successful
PR Checks / client-build (pull_request) Successful in 18s
PR Checks / frozen-manifest (pull_request) Successful in 43s
PR Checks / server-tests (pull_request) Successful in 7m48s
648d3fd2e1
The live walk rendered a generic in-app notice as "A server came online.
A server's game started..." Core's structural projection falls back to
the trigger's label and description when the payload has no title, and
on a multi-server site that never says which server. Core's rule is that
the payload wins, so every trigger now declares `title` and `intro`, and
the emitter writes the sentence ("Oxide rig is online"). An operator's
own template can still ignore it and use the parts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
whitlocktech merged commit 2f561b1103 into edge 2026-09-23 18:44:00 +00:00
whitlocktech deleted branch feat/phase-10-engagement 2026-09-23 18:44:01 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RunicGateway/Module-Rust#13
No description provided.