docs(engagement): Phase 11b's live walk — three decisions and six defects #197

Merged
whitlocktech merged 1 commits from docs/engagement-phase-11b-walk into edge 2026-09-01 12:31:50 +00:00
Member

The design of record for Phase 11b's acceptance walk, which closes the phase. Pairs with website#179, Module-uo#24 and servuo-plugins#19.

The walk was not a formality. It found six defects, four of them in code shipped by earlier phases, and none of the six was visible in a unit test — the reason being the same every time: each is a disagreement between two things that agree with each other in a fixture.

Three decisions, settled by the org lead before any code

Question the walk raised Decision
11 uo.house.idoc_warning ships delay_seconds: 900 and nothing could cancel it add uo.house.refreshed — a 26th trigger, with a body and a seeded rule of its own
12 a rule with a cooldown delivered on exactly ONE of its channels channel joins the cooldown key — a cooldown is per delivery, not per occasion
13 uo.vendor.expiring could not fire, because the market sweep does not diff fees widen BridgeMarket.Signature() with exempt + periodsRemaining

Decision 11 closes a gap §4.2a already named: a delay without a cancellation is just a late mail. The rule's cancel_on listed the outcome where the warning is pointless (the collapse) and not the one where it is wrong (the repair) — and the wire had been carrying that transition all along. It ships with a body as well, which was the org lead's call rather than the minimum: an office that only ever writes when something is wrong teaches its reader to dread the seal.

Decision 12 has the widest blast radius and had been latent since Phase 4a. The claim runs inside the engine's per-channel loop, so the first channel claimed the cooldown and every later one was reported as cooled — and inapp is ranked first deliberately, so a rule naming email and in-app delivered the inbox item and silently never the mail. Core's own news.post rule has that shape. Every in-universe body Phase 11b wrote was unreachable behind its own in-app twin. Claiming once per user before the loop was the alternative and was refused: an operator who says "one a day about this house" means one mail and one inbox item.

Decision 13 is a Phase 10 omission. fees was added to the frame and never added to the sweep's change detector, so the notification whose whole subject is a vendor running out of gold could fire only when somebody happened to reprice an item on a shop that was already broke.

Four repairs that needed no decision, because the answer was a fact

  • Every module-seeded rule failed to insertmax_sends_per_hour validated and then dropped from the normalized rule, against a NOT NULL column. The test asserted the rejection of a bad ceiling and never that a good one survives.
  • Every owner-audienced trigger resolved to nobodylink.user_id where the model returns userId, and it looks exactly like the ordinary unlinked-account case, which the code treats as normal and does not log. The test fake returned user_id and agreed with the bug, while the fake next door, for the same model, returned userId.
  • Every call-to-action in every in-universe body was a dead link — the client router prefixes module routes with the module ID, not module.json's mounts, so every declared example was a 404; and no url variable was ever populated, so the buttons rendered with an empty href and vanished.
  • A raw wire timestamp was signing off the Merchants' Guild's letter.

What the walk proved, rung by rung

Against real ServUO + the release sidecar + a live SMTP catcher: a Greatly transition produced one notification to the linked owner on both channels and nothing to a second linked-and-subscribed player; the next transition inside the day was cooled; the owner logging back in cancelled both pending rows and sent the reassurance letter; a vendor drawn to its last period warned its owner once and was cooled on the second crossing; a failed game login over a real socket produced one notice and a successful one produced none; three genuine shutdown/boot cycles produced one downtime fan-out and two fully-cooled ones; a governor seated on a live city.update produced both the town's bulletin and the crown's letter; a real staff action reached the admin and neither the editor nor the moderator, both subscribed; and the rule editor refused staff, authenticated and everyone on an admin-ceiling trigger while accepting admin.

Files

  • website/ENGAGEMENT.md — the three decisions, the four repairs, the rung-by-rung result, and the ServUO Ageless-vs-LikeNew fact. The 11b bullet and §8.6's family table now read 26 triggers / 34 bodies.
  • website/BACKEND_DESIGN.mdengagement_cooldowns gains channel in its PRIMARY KEY, with the migration's information_schema guard and why MariaDB forces one.
  • link/v5.md — the sweep has to diff the fees or the frame never comes, stated as the general rule for the next enrichment since it is emit cadence rather than frame shape.
  • modules/uo/API.md — §5.7a the cancel-shaped trigger; §5.7b every link comes from config/clientPaths.js, and the two mistakes that made every call-to-action dead.

AI-assisted: written with Claude Code.

🤖 Generated with Claude Code

The design of record for Phase 11b's acceptance walk, which closes the phase. Pairs with **website#179**, **Module-uo#24** and **servuo-plugins#19**. The walk was not a formality. It found **six defects, four of them in code shipped by earlier phases**, and none of the six was visible in a unit test — the reason being the same every time: each is a disagreement between two things that agree with each other in a fixture. ## Three decisions, settled by the org lead before any code | | Question the walk raised | Decision | |---|---|---| | **11** | `uo.house.idoc_warning` ships `delay_seconds: 900` and nothing could cancel it | **add `uo.house.refreshed`** — a 26th trigger, with a body and a seeded rule of its own | | **12** | a rule with a cooldown delivered on exactly ONE of its channels | **`channel` joins the cooldown key** — a cooldown is per delivery, not per occasion | | **13** | `uo.vendor.expiring` could not fire, because the market sweep does not diff fees | **widen `BridgeMarket.Signature()`** with `exempt` + `periodsRemaining` | **Decision 11** closes a gap §4.2a already named: a delay without a cancellation is just a late mail. The rule's `cancel_on` listed the outcome where the warning is *pointless* (the collapse) and not the one where it is *wrong* (the repair) — and the wire had been carrying that transition all along. It ships with a body as well, which was the org lead's call rather than the minimum: an office that only ever writes when something is wrong teaches its reader to dread the seal. **Decision 12 has the widest blast radius and had been latent since Phase 4a.** The claim runs inside the engine's per-channel loop, so the first channel claimed the cooldown and every later one was reported as `cooled` — and `inapp` is ranked first deliberately, so a rule naming email and in-app delivered the inbox item and *silently never the mail*. Core's own `news.post` rule has that shape. Every in-universe body Phase 11b wrote was unreachable behind its own in-app twin. Claiming once per user before the loop was the alternative and was refused: an operator who says "one a day about this house" means one mail **and** one inbox item. **Decision 13 is a Phase 10 omission.** `fees` was added to the frame and never added to the sweep's change detector, so the notification whose whole subject is a vendor running out of gold could fire only when somebody happened to reprice an item on a shop that was already broke. ## Four repairs that needed no decision, because the answer was a fact - **Every module-seeded rule failed to insert** — `max_sends_per_hour` validated and then dropped from the normalized rule, against a `NOT NULL` column. The test asserted the *rejection* of a bad ceiling and never that a good one survives. - **Every `owner`-audienced trigger resolved to nobody** — `link.user_id` where the model returns `userId`, and it looks exactly like the ordinary unlinked-account case, which the code treats as normal and does not log. **The test fake returned `user_id` and agreed with the bug**, while the fake next door, for the same model, returned `userId`. - **Every call-to-action in every in-universe body was a dead link** — the client router prefixes module routes with the module **ID**, not `module.json`'s `mounts`, so every declared `example` was a 404; and no `url` variable was ever populated, so the buttons rendered with an empty href and vanished. - **A raw wire timestamp was signing off the Merchants' Guild's letter.** ## What the walk proved, rung by rung Against real ServUO + the release sidecar + a live SMTP catcher: a `Greatly` transition produced **one** notification to the linked owner on both channels and nothing to a second linked-and-subscribed player; the next transition inside the day was cooled; the owner logging back in **cancelled both pending rows** and sent the reassurance letter; a vendor drawn to its last period warned its owner once and was cooled on the second crossing; a failed game login over a real socket produced one notice and a **successful** one produced none; three genuine shutdown/boot cycles produced **one** downtime fan-out and two fully-cooled ones; a governor seated on a live `city.update` produced **both** the town's bulletin and the crown's letter; a real staff action reached the admin and **neither the editor nor the moderator**, both subscribed; and the rule editor refused `staff`, `authenticated` and `everyone` on an `admin`-ceiling trigger while accepting `admin`. ## Files - **`website/ENGAGEMENT.md`** — the three decisions, the four repairs, the rung-by-rung result, and the ServUO `Ageless`-vs-`LikeNew` fact. The 11b bullet and §8.6's family table now read 26 triggers / 34 bodies. - **`website/BACKEND_DESIGN.md`** — `engagement_cooldowns` gains `channel` in its PRIMARY KEY, with the migration's `information_schema` guard and why MariaDB forces one. - **`link/v5.md`** — the sweep has to *diff* the fees or the frame never comes, stated as the general rule for the next enrichment since it is emit cadence rather than frame shape. - **`modules/uo/API.md`** — §5.7a the cancel-shaped trigger; §5.7b every link comes from `config/clientPaths.js`, and the two mistakes that made every call-to-action dead. --- AI-assisted: written with Claude Code. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
wtclaude added 1 commit 2026-09-01 12:16:05 +00:00
The walk is the last piece of 11b and it was not a formality. It found six
defects, four of them in code shipped by earlier phases, and none of the six was
visible in a unit test: each is a disagreement between two things that agree with
each other in a fixture.

Three of the fixes were decisions rather than repairs, all settled by the org
lead before any code:

  11  `uo.house.idoc_warning` ships delay_seconds: 900 and nothing could cancel
      it  ->  add `uo.house.refreshed`, a 26th trigger with a body and a rule
  12  a rule with a cooldown delivered on exactly ONE of its channels
          ->  `channel` joins the cooldown key; a cooldown is per DELIVERY
  13  `uo.vendor.expiring` could not fire, because the market sweep does not
      diff fees  ->  widen BridgeMarket.Signature() with exempt +
      periodsRemaining

Files:

  website/ENGAGEMENT.md   the three decisions, the four repairs, and what the
                          walk proved rung by rung; the 11b bullet and the §8.6
                          family table now read 26 triggers / 34 bodies
  website/BACKEND_DESIGN.md  engagement_cooldowns gains `channel` in its PRIMARY
                          KEY, with the migration's information_schema guard and
                          why MariaDB forces one
  link/v5.md              the sweep has to DIFF the fees or the frame never
                          comes -- stated as the general rule for the next
                          enrichment, since it is emit cadence and not shape
  modules/uo/API.md       §5.7a the cancel-shaped trigger and the Ageless-vs-
                          LikeNew ServUO fact; §5.7b every link comes from
                          config/clientPaths.js, and the two mistakes that made
                          every call-to-action a dead link

Pairs with website#<core>, Module-uo#<uo> and servuo-plugins#<plugin>.

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech merged commit 0105e294d5 into edge 2026-09-01 12:31:50 +00:00
whitlocktech deleted branch docs/engagement-phase-11b-walk 2026-09-01 12:31:50 +00:00
Sign in to join this conversation.
No description provided.