The reviewer/planner gate in fusion was rejecting the original
incident-routing payload because it lacked the markers fusion expects
of an engineering specification. The triage agent looped on PROMPT.md
revisions because each rewrite still had no explicit Mission, File
Scope, Steps with verifiable outcomes, Testing Requirements, or
Acceptance Criteria.
Reshape buildIncidentDescription so the task body is recognized as a
spec on first pass: explicit Mission line, Background (preserves the
alert + signal context the agent needs to investigate), domain-aware
File Scope with allowed/disallowed paths, five numbered Steps each
with its own acceptance line, Dependencies, Testing Requirements
referencing the incident fingerprint, Documentation Deliverables for
the executor's work-log + QA's fix-patterns memory entry, Acceptance
Criteria checklist, and severity-aware Routing Notes. The raw Grafana
payload is kept at the bottom as an audit-trail block.
fileScopeForDomain() picks include/exclude lists from the upstream
domain hint: backend → API surface, frontend/product → web surface,
unknown → API. Critical-path directories (auth, payments, billing,
subscriptions, migrations, schema, ecosystem.config.js, lockfile)
are always in the disallow list so the executor stops before
modifying them — keeps human-approval policy enforceable.
@fusion/plugin-sdk's package.json points its import entry at
src/index.ts (TS source), and that file imports core/src/plugin-types.js
via relative path. Node 22 ESM has no TS loader so the chain
unraveled at runtime: the plugin loader successfully resolved our
compiled dist/index.js, but the very first `import { definePlugin }
from "@fusion/plugin-sdk"` walked into TS source and fell over.
definePlugin is a pure typed identity function — it adds no runtime
behavior. Inline its source plus a structural copy of the plugin
context/route/manifest types we use, drop the @fusion/plugin-sdk
dependency entirely. The compiled output now imports nothing outside
node:crypto and Node builtins, so it can load anywhere fusion can
spawn ESM modules.
When fusion publishes a compiled SDK build, swap the inline types
back to import statements and restore the workspace dependency.
The runtime plugin packages (paperclip, hermes, openclaw) all build to
dist/ and have package.json exports import pointing at dist/index.js.
Fusion's plugin loader uses Node ESM dynamic import on the absolute
installation path, which fails on TypeScript source because there's
no TS loader at runtime. Drop noEmit, exclude tests from compilation,
emit dist + sourcemaps + d.ts so the loader can resolve dist/index.js
the same way it resolves paperclip's.
Plugin should be re-registered with path
/app/plugins/fusion-plugin-telemetry-watcher/dist/index.js after the
fusion redeploy lands the new dist artifact.
Fusion's plugin loader imports source files directly via Node 22 ESM
without a TS loader, so relative imports like "./internal/dedup.js"
fail at runtime: there's no compiled .js artifact and Node won't fall
back to .ts. Inline severity classifier, dedup cache, rate limiter,
and Grafana parser into the single entry file. Tests now import named
exports from "../index.js" directly. The Phase-2 split into separate
source files can come back once fusion adds a TS-aware plugin loader.
All 14 unit tests still pass. Behavior unchanged.
New workspace package fusion-plugin-telemetry-watcher that turns a
Grafana Alerting webhook payload into a Fusion incident task in the
triage column. Hooks the dedup/severity/rate-limit primitives that
PostHog/Sentry/Slack sources will reuse in Phase 2.
Pipeline:
POST /api/plugins/fusion-plugin-telemetry-watcher/grafana-webhook
→ bearer-secret check
→ parseGrafanaPayload (one signal per firing alert; resolved alerts
are dropped — recovery is verified post-deploy by the QA agent)
→ classifySeverity P0/P1/P2/P3 with critical-path keyword
escalation (payment/auth/billing/subscription)
→ DedupCache 4h fingerprint window — repeat fires log against the
existing task instead of opening duplicates
→ IncidentRateLimiter 5/h, 20/d — overflow becomes a "telemetry
storm" mega-task in a future phase
→ taskStore.createTask({ column: "triage", priority })
→ optional auto-assign to Triage Agent
14 unit tests cover severity buckets, critical-path escalation, dedup
windowing/eviction, hourly+daily rate caps, and the Grafana payload
parser (firing vs resolved, label-based domain inference).
Settings expose all thresholds + secret + dedup window + rate limits
through the dashboard plugin settings UI. README documents the deploy
+ register + Grafana contact-point wiring.