# Notion Hook Hardening Report

Date: 2026-03-14 (America/Los_Angeles)

## Scope Completed
Hardened `/Users/openclaw/event-router/router.js` so only authoritative Notion comments from Braden that explicitly mention `@Dorian` are eligible for forwarding to OpenClaw hooks.

## Files Changed
1. `/Users/openclaw/event-router/router.js`
   - Removed prior verification-token capture/echo-all forwarding behavior.
   - Added strict gating logic for `comment.created` only.
   - Added Notion API comment resolution (`GET /v1/comments/{id}`) using API key from `/Users/openclaw/.openclaw/openclaw.json` (`skills.entries.notion.apiKey`).
   - Added author allow policy:
     - ID match: `889e1697-85f9-4892-a8dd-3ae6512a894e`, OR
     - display name match: `Braden McLeish`.
   - Added mention requirement: comment body must contain `@Dorian` (case-insensitive regex).
   - Added dedupe check by comment ID before forward.
   - Added robust structured logging for matched/ignored/error reasons without logging secrets.
   - Kept forward payload with `deliver: false`.
2. `/Users/openclaw/event-router/state/notion-dedupe.json` (new)
   - State file for seen comment IDs.

## Policy Behavior Matrix

| Condition | Result | HTTP | Forwarded |
|---|---|---:|---:|
| Non-POST `/webhook/notion` | Health-compatible `ok` response | 200 | No |
| JSON contains `challenge` | Return challenge JSON (minimal verification support) | 200 | No |
| Invalid JSON | Ignored (`invalid_json`) | 200 | No |
| Event type != `comment.created` | Ignored (`event_type_not_comment_created`) | 200 | No |
| `comment.created` missing comment ID | Ignored (`missing_comment_id`) | 200 | No |
| Duplicate comment ID (already seen) | Ignored (`duplicate_comment_id`) | 200 | No |
| Comment author not Braden | Ignored (`author_not_braden`) | 200 | No |
| No `@Dorian` mention | Ignored (`missing_dorian_mention`) | 200 | No |
| All policy checks pass | Sent to OpenClaw hook (`deliver:false`) + dedupe state write | 200 | Yes |
| Internal exception (e.g., Notion fetch failure) | Error payload | 500 | No |

## Validation Performed

### 1) LaunchAgent restart
Command:
```bash
launchctl kickstart -k gui/$(id -u)/ai.openclaw.event-router
launchctl print gui/$(id -u)/ai.openclaw.event-router | head -n 40
```
Result: service running with updated program path `/Users/openclaw/event-router/router.js`.

### 2) Local health check
Command:
```bash
curl -sS http://127.0.0.1:8080/health
```
Output:
```json
{"ok":true,"service":"event-router","port":8080}
```

### 3) Simulated non-comment event ignored
Command:
```bash
curl -sS -X POST http://127.0.0.1:8080/webhook/notion \
  -H 'Content-Type: application/json' \
  -d '{"type":"page.created","id":"evt_non_comment_1"}'
```
Output:
```json
{"ok":true,"forwarded":false,"ignored":"event_type_not_comment_created","eventType":"page.created"}
```

### 4) Simulated comment-created path
Command:
```bash
curl -sS -i -X POST http://127.0.0.1:8080/webhook/notion \
  -H 'Content-Type: application/json' \
  -d '{"type":"comment.created","comment":{"id":"00000000-0000-0000-0000-000000000000"}}'
```
Observed:
- Attempted Notion comment fetch as designed.
- Notion API returned object-not-found for the fake comment ID.
- Router returned 500 with explicit fetch-failure message.

Note: This validates the `comment.created` fetch path is active. A full success-path validation requires a real accessible Notion comment ID that satisfies author + mention policy.

## Logging Check
`/Users/openclaw/event-router/router.log` now records concise, structured entries like:
- ignored reason + context (`event_type_not_comment_created`, etc.)
- handler_error with sanitized error message
- forwarded status metadata (`commentId`, `eventType`, `openclawStatus`, `deliver:false`)

No tokens/secrets are emitted by new logging paths.

## Rollback Instructions
If rollback is required:
1. Restore previous router source (if backup available):
   - e.g. `/Users/openclaw/event-router/router.js.bak.*`
2. Restart service:
```bash
launchctl kickstart -k gui/$(id -u)/ai.openclaw.event-router
```
3. Confirm health:
```bash
curl -sS http://127.0.0.1:8080/health
```
4. Optional: remove dedupe state file if reverting feature entirely:
```bash
rm -f /Users/openclaw/event-router/state/notion-dedupe.json
```
