Repository navigation
[Stage 1 / P0] governance: event log + per-request authorize + anomaly channel #24
Description
[Stage 1 / P0] governance: event log + per-request authorize + anomaly channel
Author: @modacker
Source review:docs/REVIEW-sihankor-baselines-2026-08-28.mdPart 4
Tier 1 items #1 (first-run token modal) deferred to Stage 2; this
issue bundles the three work items that shareserver/lib/events.js
as their foundation.
Source borrow:docs/BORROW-dsh-deepseek-harness-2026-08-28.md
borrows #1 (event log) and #2 (authorize).
Related PR: modacker/MiniMax-Code-Plugins#23 (round 7
Token Plan integration already shipped; this is the next
governance layer).
Why
The mcode-webui plugin is a thin shell over the mcode agent runtime.
Today it has no audit trail, no authorization envelope for
destructive actions, and no anomaly channel. Three SiHankor
engineering baselines fail:
- Baseline 1 (LLM is symbol-material generator, not governor):
destructive routes (DELETE /api/sessions/:id, slash
delete/clear/reset) are executed as side effects of an LLM
reply, with no user-facing confirmation beyond a hidden modal. - Baseline 3 (human attention only to anomaly signals): errors
get appended tocs.chatas! [error] msg, mixed with normal
flow. The user has to scan the whole conversation to find them. - Baseline 4 (verifiability — traceable / checkable / tamper-proof):
settings.jsonandsessions.jsonare full-file overwrites, no
diff, no transaction, no checksum. ConcurrentPOST /api/settings
would race. There is no event log anywhere.
The deepseek-harness (dsh) project demonstrates a clean pattern
for all three: append-only SessionEvent log + per-request
"ask human" authorization + /api/alerts SSE channel. dsh's
package boundaries map cleanly to plugin-scope work in
mcode-webui.
What
Three changes, sharing a new server/lib/events.js module. They
must land together because the authorize helper and the anomaly
channel both write event-log entries, and the event log itself
needs the same append-only semantics.
1. server/lib/events.js — append-only event log
NDJSON line per write to ~/.mcode-webui/events.ndjson. Inspired
by dsh's SessionEvent row format:
{
"seq": <monotonic>,
"ts": "<ISO-8601>",
"actor": "user" | "mcode" | "system",
"kind": "settings.write" | "sessions.create" | "sessions.delete"
| "upload.create" | "slash.exec" | "authz.request"
| "authz.resolve" | "alert.raise",
"target": "<file path or session id>",
"before_hash": "<sha256 of prev state or 'null'>",
"after_hash": "<sha256 of new state or 'null'>",
"cid": "<connection id from state-bus>",
"data": { ...event-specific payload... }
}Properties (copied from dsh):
- Append-only — never update, never delete (except by retention
policy) - Monotonic sequence — easy to detect gaps
- Hash-chained:
after_hashincludesprev_after_hashfor
tamper-evidence - 200 ms write-behind window for high-frequency events (chat ticks)
- Reverse + forward read passes for replay
Effort: ~300 lines for events.js + tests, plus ~50 lines per write
site.
2. server/lib/authorize.js — per-request "ask human"
// in routes/sessions.js
const r = await authorize({ action: 'delete-session', sessionId: id, ctx });
if (!r.ok) return; // user declined or timed out
// ... existing delete logicHelper that:
- Sends a
needs_authorizationevent to the requesting connection's
SSE channel (viastate-bus.js) - Blocks the action in a Promise
- Resolves when the user clicks allow/deny in the UI
- Times out with
AuthorizationDeclinedErrorafter N seconds
(default 5 min) - Writes an
events.ndjsonline for both the request and the
resolution
Wrap destructive routes with this:
DELETE /api/sessions/:id, slash delete / clear / reset,
token rotation (if you want explicit confirmation), workspace reset.
This is the cleanest way to enforce SiHankor prohibition 1 ("LLM
must not directly modify knowledge/intent") without rewriting
the host.
Effort: ~250 lines for the helper + 4–5 wrapping sites.
3. server/lib/alerts.js + /api/alerts — anomaly channel
Parallel to state-bus.js and /api/events. Replace
console.warn for user-facing errors with
pushAlert({ level, msg, src }).
Frontend:
- Top-right bell icon with unread count, click → toast history
cs.chat's! [error]becomes a secondary indicator that
highlights when an alert is live
Effort: ~300 lines across server + frontend.
Acceptance criteria
-
events.ndjsonexists after a settings change, contains
before_hash/after_hash/actor/kind/cid - Tampering with a row (delete or modify) is detectable by
forward + reverse read pass (test intest/lib-events.test.js) - Concurrent
POST /api/settingsfrom two connections does
NOT race (test intest/lib-events-concurrency.test.js) -
DELETE /api/sessions/:idshows a top-bar modal asking for
confirmation; declining in the modal leaves the session
intact; the action emits 2 events (request + resolve) - A server-side error in any route pushes to
/api/alerts,
the top-bar bell increments, the toast history shows it -
npm testpasses (target 450/0/2) - SiHankor Baseline 1 + 3 + 4 + prohibition 1 + 4 + 5 status
changes from ✗ to ✓ in the review doc
Out of scope (covered in other issues)
- First-run token modal — see Stage 2 issue
?token=drop on first access — see Stage 2 issue- Slash command autocomplete — see Stage 2 issue
- mcode-side hooks (
event:audit,event:anomaly,
permission:request) — see host-scope issue
Activity
modacker commentedon Sep 17, 2026
Withdrawing alongside the mcode-webui PR closures (#23/#31) — with the marketplace submission pulled, this proposal no longer has an owner here. Re-open if it becomes relevant again.