fix(events): carry a module's own account of a successful step #197
Reference in New Issue
Block a user
No description provided.
Delete Branch "fix/events-module-detail"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
EVENTS.md§H told a module the revert contract accepts adetailon its envelope.classify()readsok,retry,error,await,holdFor,resourcesandparticipants— and has never read adetail. So a module that answered one was writing into nothing.module-uobelieved it, twice, since Phase 12b:uo.item.grant{ granted, missed, why }uo.world.save{ started: true }The grant is the one that mattered. A grant reaches the players a run's participation ledger holds, and which of them missed out is knowable only to the module — so an operator saw a step marked
doneand never learned four of twelve got nothing.Found writing the integration kit's chapter 5 (
Integration-kit#10), whose template made the same mistake on §H's authority. Fixed here by making the member real rather than by deleting the reporting, because §H's sentence was right and only its example was wrong.What this adds
detailbecomes an optional member of the two success envelopes, besideresourcesandparticipants— on both, becauseawait: 'human'is a success and a cue's confirm finishes the step without a second dispatch, so that is the only moment its module could ever have said anything.Core never interprets it.
safeDetail()bounds it and nothing else reads a key out of it — not in the dispatcher, not in the runner, not in the browser. That is the point: a module knows things about its own verb core cannot compute, and it had no other way to say them.toJSON). Reaching the runner would make the logINSERTthrow instead — inside the one write documented never to.participantstakes.The runner writes a
step.detailrun-log row — its own kind rather than a field onresource.recorded, because the grant that forced this ledgers nothing (reversible: 'none') and reports no participants, so it would have had nowhere to ride.The renderer is half the fix
describeLogLine's default returns a kind word, so astep.detailrow falling through to it would have rendered as the literal stringstep.detail— the channel existing and showing nothing, which is exactly the failure being fixed. It gets a case that renders whatever keys the module put there, generically: a switch on known keys would be the browser learning one module's vocabulary, which is the thing the module system exists to prevent.Lists are capped at five with "and N more" and values at 80 characters, so one row stays one row.
module-uoneeds no changeThe code it already shipped starts working, unchanged. Verified against the real classifier:
The failure channel is unchanged and is still
erroralone.Checks
npm run check:modules— core names no module identifierMODULE_API_VERSIONstays 1.10.0, amended in place — still onedgePairs with docs#226.
🤖 Generated with Claude Code
https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
`EVENTS.md` §H told a module the revert contract accepts a `detail` on its envelope. `classify()` reads `ok`, `retry`, `error`, `await`, `holdFor`, `resources` and `participants` — and has never read a `detail`. So a module that answered one was writing into nothing. `module-uo` believed it, twice, since Phase 12b: * `uo.item.grant` answers `{ granted, missed, why }` * `uo.world.save` answers `{ started: true }` The grant is the one that matters. A grant reaches the players a run's participation ledger holds, and **which of them missed out is knowable only to the module and reported nowhere else** — so an operator saw a step marked `done` and never learned four of twelve got nothing. Found writing the integration kit's chapter 5 (`Integration-kit#10`), whose template made the same mistake on §H's authority. ## What this adds `detail` becomes a real, optional member of the two SUCCESS envelopes, beside `resources` and `participants` — on both, because `await: 'human'` is a success and a cue's confirm finishes the step without a second dispatch, so that is the only moment its module could ever have said anything. **Core never interprets it.** `safeDetail()` bounds it and nothing else reads a key out of it, here or in the runner or in the browser. That is the point: a module knows things about its own verb core cannot compute, and it had no other way to say them. * objects only — the column is JSON and the console renders keys, so a bare string has nothing to render under, and core inventing a key would be core interpreting it after all; * 4KB of serialised JSON, dropped rather than truncated, because half a JSON object is not a JSON object; * unserialisable (circular, a throwing `toJSON`) is dropped — reaching the runner would make the log INSERT throw, inside the one write documented never to; * re-parsed rather than passed through, so core holds no live reference into a module's object; * **anything wrong with it is dropped and logged, never a failure.** A step that did what it was asked must not be re-run because its module's commentary was malformed: that is a world write repeated for a log line. The runner writes it as a `step.detail` run-log row, its own kind rather than a field on `resource.recorded` — the grant that forced this ledgers nothing (`reversible: 'none'`) and reports no participants, so it would have had nowhere to ride. ## The renderer, which is half the fix `describeLogLine`'s default returns a kind WORD, so a `step.detail` row falling through would have rendered as the literal string "step.detail" — the channel existing and showing nothing, exactly the failure being fixed. It gets a case that renders whatever keys the module put there, generically: a switch on known keys would be the browser learning one module's vocabulary. uo.item.grant — granted: 8, missed: 4, why: bank full, offline uo.world.save — started: true **`module-uo` needs no change**: the code it already shipped starts working. MODULE_API stays 1.10.0, amended in place — it is still on `edge`. Zero-line route manifest diff; no route added. 2057 server tests, 400 client tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4