plugin(telemetry-watcher): write task body as engineering spec
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.
This commit is contained in:
@@ -463,41 +463,168 @@ function severityToPriority(severity: Severity): "low" | "normal" | "high" | "ur
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the file-scope hints for the executor based on the upstream alert's
|
||||
* domain. Conservative — when uncertain we cover both surfaces so the
|
||||
* executor doesn't have to chase a missing path. Critical paths are always
|
||||
* listed in "out of scope" to give the reviewer a clean veto axis.
|
||||
*/
|
||||
function fileScopeForDomain(domain: string): { include: string[]; exclude: string[] } {
|
||||
const sharedExclude = [
|
||||
"apps/api/src/modules/auth/**",
|
||||
"apps/api/src/modules/payments/**",
|
||||
"apps/api/src/modules/billing/**",
|
||||
"apps/api/src/modules/subscriptions/**",
|
||||
"apps/api/src/database/migrations/**",
|
||||
"apps/api/src/database/schema/**",
|
||||
"apps/web/src/routes/_auth/**",
|
||||
"apps/web/src/routes/dashboard/billing/**",
|
||||
"package.json",
|
||||
"pnpm-lock.yaml",
|
||||
"ecosystem.config.js",
|
||||
];
|
||||
|
||||
if (domain === "frontend") {
|
||||
return {
|
||||
include: [
|
||||
"apps/web/src/**",
|
||||
"apps/web/test/**",
|
||||
"apps/web/e2e/**",
|
||||
],
|
||||
exclude: sharedExclude,
|
||||
};
|
||||
}
|
||||
if (domain === "product") {
|
||||
return {
|
||||
include: [
|
||||
"apps/web/src/routes/**",
|
||||
"apps/web/src/components/**",
|
||||
"apps/web/src/messages/**",
|
||||
"apps/web/test/**",
|
||||
],
|
||||
exclude: sharedExclude,
|
||||
};
|
||||
}
|
||||
// backend / unknown / infra → API surface
|
||||
return {
|
||||
include: [
|
||||
"apps/api/src/modules/**",
|
||||
"apps/api/src/integrations/**",
|
||||
"apps/api/src/telemetry/**",
|
||||
"apps/api/src/redis/**",
|
||||
"apps/api/src/jobs/**",
|
||||
"apps/api/test/**",
|
||||
],
|
||||
exclude: sharedExclude,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the engineering specification body for the incident task.
|
||||
*
|
||||
* The output is intentionally shaped to satisfy fusion's planning + reviewer
|
||||
* gate: explicit Mission, File Scope (include/exclude), Steps with verifiable
|
||||
* outcomes, Dependencies, Testing Requirements, Documentation Deliverables,
|
||||
* and Acceptance Criteria. The triage payload (alert summary, severity,
|
||||
* fingerprint, raw signal context) is preserved as Background — it tells the
|
||||
* planner what to investigate without crowding out the spec.
|
||||
*/
|
||||
function buildIncidentDescription(
|
||||
parsed: ParsedGrafanaSignal,
|
||||
severity: Severity,
|
||||
fingerprint: string,
|
||||
rawPayloadSnippet: string,
|
||||
): string {
|
||||
const scope = fileScopeForDomain(parsed.signal.domain);
|
||||
const summary = parsed.description || "(no summary provided by Grafana)";
|
||||
const verificationHint =
|
||||
parsed.signal.meta?.endpoint
|
||||
? `the alert metric for endpoint \`${String(parsed.signal.meta.endpoint)}\` (Tempo P95 / error rate / Faro RUM, depending on the original alert)`
|
||||
: `the originating Grafana alert query`;
|
||||
|
||||
return [
|
||||
`## Incident from telemetry-watcher`,
|
||||
`# Mission`,
|
||||
`Diagnose and resolve the production regression that triggered Grafana alert **${parsed.alertname}** (severity ${severity}).`,
|
||||
``,
|
||||
`**Severity:** ${severity}`,
|
||||
`## Background`,
|
||||
`**Source:** Grafana Alerting`,
|
||||
`**Alert:** ${parsed.alertname}`,
|
||||
`**Severity:** ${severity}`,
|
||||
`**Domain hint:** ${parsed.signal.domain}`,
|
||||
`**Fingerprint:** \`${fingerprint}\``,
|
||||
``,
|
||||
`### Summary`,
|
||||
parsed.description || "(no summary provided by Grafana)",
|
||||
`**Alert summary:** ${summary}`,
|
||||
``,
|
||||
`### Signal context`,
|
||||
`**Signal context:**`,
|
||||
"```json",
|
||||
JSON.stringify(parsed.signal.meta ?? {}, null, 2),
|
||||
"```",
|
||||
``,
|
||||
`### Triage Agent: next steps`,
|
||||
`1. Read \`.fusion/memory/team-charter.md\` for routing rules.`,
|
||||
`2. Verify the alert with \`fn_query_grafana_tempo\` / \`fn_query_grafana_loki\` (Phase 2 tools).`,
|
||||
`3. Per-severity routing:`,
|
||||
` - **P0** → open council task (CEO + CTO + CPO).`,
|
||||
` - **P1** → delegate directly to CTO with \`cto/brief\`.`,
|
||||
` - **P2** → delegate directly to BE/FE Eng per domain hint.`,
|
||||
` - **P3** → record in project memory under \`fix-patterns\`; do NOT open a task — close this one.`,
|
||||
`4. If domain is \`unknown\`, classify before delegating.`,
|
||||
`5. If this fingerprint hits the dedup window again, append a log instead of opening a new task.`,
|
||||
`## File Scope`,
|
||||
``,
|
||||
`### Raw payload (truncated)`,
|
||||
`**Allowed paths (executor may modify):**`,
|
||||
...scope.include.map((p) => `- \`${p}\``),
|
||||
``,
|
||||
`**Out of scope (executor MUST NOT modify without explicit human approval):**`,
|
||||
...scope.exclude.map((p) => `- \`${p}\``),
|
||||
``,
|
||||
`## Steps`,
|
||||
``,
|
||||
`1. **Reproduce the regression.**`,
|
||||
` - Pull traces/logs/RUM events for the affected surface using the Grafana evidence in Background.`,
|
||||
` - Identify the offending code path (e.g. specific upstream integration, slow query, blocking I/O).`,
|
||||
` - Acceptance: write your findings to \`executor/work-log\` with concrete trace excerpts or log fingerprints.`,
|
||||
``,
|
||||
`2. **Write a failing regression test.**`,
|
||||
` - Add a Vitest (or Playwright for UI) test that fails on the current dev HEAD without the fix.`,
|
||||
` - The test name must reference incident fingerprint \`${fingerprint}\` so future triage can find it.`,
|
||||
` - Acceptance: \`pnpm test\` shows the new test red on a fresh checkout.`,
|
||||
``,
|
||||
`3. **Apply the smallest fix.**`,
|
||||
` - Stay inside the Allowed paths above. If you discover the real fix lives in an Out-of-scope path, STOP and surface it via \`fn_send_message\` to the user.`,
|
||||
` - Do not refactor unrelated code. File adjacent issues as separate tasks via \`fn_task_create\` if you find them.`,
|
||||
` - Acceptance: regression test from step 2 passes.`,
|
||||
``,
|
||||
`4. **Improve observability if the metric was missing or coarse.**`,
|
||||
` - If the alert fired but the trace lacked the dimension that pointed to the root cause, add a counter/histogram label in \`apps/api/src/telemetry/metrics.ts\` (or Faro instrumentation for the frontend).`,
|
||||
` - Acceptance: new dimension visible in next OTEL/Faro export.`,
|
||||
``,
|
||||
`5. **Run all quality gates.**`,
|
||||
` - \`pnpm lint\`, \`pnpm typecheck\`, \`pnpm test\` must be green before moving the task to in-review.`,
|
||||
` - Acceptance: zero failures across the three commands.`,
|
||||
``,
|
||||
`## Dependencies`,
|
||||
`None. This task is independent.`,
|
||||
``,
|
||||
`## Testing Requirements`,
|
||||
`- New regression test in the appropriate \`apps/*/test/\` directory referencing fingerprint \`${fingerprint}\`.`,
|
||||
`- Full suite (\`pnpm test\`) must remain green.`,
|
||||
`- For UI fixes, add a Playwright case under \`apps/web/e2e/\` that exercises the user-facing scenario.`,
|
||||
`- Manual verification post-deploy: ${verificationHint} should recover toward the pre-incident baseline within 30 minutes of the dev.sase.tr deploy.`,
|
||||
``,
|
||||
`## Documentation Deliverables`,
|
||||
`- \`executor/work-log\` task document: trace evidence, root-cause one-liner, regression test name, observability additions, list of any out-of-scope follow-ups filed.`,
|
||||
`- After post-deploy verification confirms recovery, QA Lead appends an entry to project memory namespace \`fix-patterns\` (symptom, root cause, fix summary, prevention).`,
|
||||
``,
|
||||
`## Acceptance Criteria`,
|
||||
`- [ ] Regression test exists and references fingerprint \`${fingerprint}\`.`,
|
||||
`- [ ] \`pnpm lint\`, \`pnpm typecheck\`, \`pnpm test\` all pass.`,
|
||||
`- [ ] No file outside the Allowed File Scope was modified (CI/reviewer will reject if violated).`,
|
||||
`- [ ] \`executor/work-log\` document is populated.`,
|
||||
`- [ ] Recovery metric returns to baseline ±10% within 30 minutes of the dev.sase.tr deploy. (QA Lead verifies; on miss, the task is reopened with the recovery delta in its log.)`,
|
||||
``,
|
||||
`## Routing Notes`,
|
||||
`- ${
|
||||
severity === "P0"
|
||||
? "**P0** — open council task (CEO + CTO + CPO) before any executor delegation per `team-charter.md`."
|
||||
: severity === "P1"
|
||||
? "**P1** — CTO writes `cto/brief`, then delegates to Backend Eng (or Frontend Eng if domain hint is `frontend`/`product`)."
|
||||
: severity === "P2"
|
||||
? "**P2** — direct executor delegation per domain hint."
|
||||
: "**P3** — record pattern in memory; close as informational without executor work."
|
||||
}`,
|
||||
`- If the same fingerprint reappears within the dedup window the plugin will log it against this task instead of opening a new one.`,
|
||||
``,
|
||||
`## Raw payload (audit trail)`,
|
||||
"```json",
|
||||
rawPayloadSnippet,
|
||||
"```",
|
||||
|
||||
Reference in New Issue
Block a user