Skip to content

Commit f09715d

Browse files
committed
feat(agent-workspace): add notification anomaly reporting
1 parent a211e0d commit f09715d

8 files changed

Lines changed: 349 additions & 5 deletions

docs/brainstorms/2026-04-16-mainline-ci-stabilization-and-m7-direction-requirements.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,36 @@ Deliverables:
613613
- `npm run test:agent-workspace:contracts`
614614
- `npm run verify:agent-workspace:runtime`
615615

616+
### M7.20 (Now): Notification Anomaly Surfacing and Retention-Policy Governance (Lane Ops Bridge)
617+
618+
Deliverables:
619+
620+
- add bounded notification anomaly-report route for operator escalation hygiene review.
621+
- expose explicit retention-policy semantics instead of hiding them in implementation details.
622+
- keep anomaly synthesis deterministic and sourced from current notification trail state.
623+
624+
#### M7.20 Progress Note (2026-04-16)
625+
626+
- [Done] expanded `src/server.ts` with notification anomaly route:
627+
- `GET /api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notification-anomalies?limit=...`.
628+
- [Done] expanded notification-policy governance output:
629+
- `/triage/remediation/escalation/notification-policy` now includes bounded `retentionPolicy`.
630+
- [Done] added deterministic anomaly/retention helper stack:
631+
- `getAgentWorkspaceDiagnosticsRemediationEscalationNotificationRetentionPolicy(...)`,
632+
- `buildAgentWorkspaceDiagnosticsRemediationEscalationNotificationAnomalyReport(...)`.
633+
- [Done] anomaly synthesis now surfaces at least:
634+
- suppression dominance over emitted deliveries,
635+
- digest throttle backlog,
636+
- stale-cleanup application when cleanup removed stale reminder records.
637+
- [Done] expanded evidence coverage:
638+
- `src/server.migration.test.ts` now validates retention-policy payload and anomaly-report route semantics.
639+
- `src/knowledge.api.contract.test.ts` now fail-fast checks notification-anomalies route contract.
640+
- `src/agent_workspace.verification.contract.test.ts` + `scripts/verify-agent-workspace-runtime.js` now fail fast on anomaly/retention helper and route drift.
641+
- [Done] verification evidence:
642+
- `npm test -- src/server.migration.test.ts --runInBand --testNamePattern "escalation notification anomaly surfacing and retention-policy governance stay deterministic"`
643+
- `npm run test:agent-workspace:contracts`
644+
- `npm run verify:agent-workspace:runtime`
645+
616646
## Success Criteria
617647

618648
- CI failure mode that previously blocked the three agent-workspace suites is eliminated on mainline.
@@ -622,4 +652,4 @@ Deliverables:
622652

623653
## Next Step
624654

625-
Proceed to `/prompts:ce-plan` using this document as the source for `M7.20` decomposition (notification anomaly surfacing and retention-policy governance), while preserving M7 lane boundary constraints.
655+
Proceed to `/prompts:ce-plan` using this document as the source for `M7.21` decomposition (notification escalation SLOs and anomaly-threshold governance), while preserving M7 lane boundary constraints.

docs/diataxis/en/explanation/development-progress-dashboard.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,25 @@ Execution anchor:
580580
- `npm run test:agent-workspace:contracts`
581581
- `npm run verify:agent-workspace:runtime`
582582

583+
## Latest Mainline Increment (2026-04-16 M7.20 Notification Anomaly Surfacing and Retention-Policy Governance Lane)
584+
585+
- Expanded `src/server.ts` with notification anomaly route:
586+
- `GET /api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notification-anomalies?limit=...`.
587+
- Expanded notification governance payload:
588+
- `/triage/remediation/escalation/notification-policy` now includes explicit `retentionPolicy` with bounded trail and digest-history semantics.
589+
- Added deterministic anomaly synthesis:
590+
- suppression-dominates-delivery warning,
591+
- digest-throttle-backlog info signal,
592+
- stale-cleanup-applied warning when stale reminder records were pruned.
593+
- Expanded executable evidence:
594+
- `src/server.migration.test.ts` now validates retention-policy payload and anomaly-report route semantics.
595+
- Hardened runtime verification gate:
596+
- `src/knowledge.api.contract.test.ts`, `src/agent_workspace.verification.contract.test.ts`, and `scripts/verify-agent-workspace-runtime.js` now fail fast on notification-anomalies route and retention/anomaly helper drift.
597+
- Verification evidence:
598+
- `npm test -- src/server.migration.test.ts --runInBand --testNamePattern \"escalation notification anomaly surfacing and retention-policy governance stay deterministic\"`
599+
- `npm run test:agent-workspace:contracts`
600+
- `npm run verify:agent-workspace:runtime`
601+
583602
## Mainline vs Working-Branch Snapshot (2026-04-14)
584603

585604
| Capability Slice | Working Branch (`feat/learning-multi-tutor-adapter`) | Mainline (`origin/main`) | Integration Status |
@@ -628,7 +647,7 @@ This dashboard aligns against the following requirement chain:
628647
| L2 Retrieval | explainable hybrid/vector retrieval + governance | Expanded in branch-oriented plans | Mainline file-backed baseline only (`src/learning/store.ts`) | Re-enter lane after concrete module evidence lands on mainline |
629648
| L3 Learning | mastery diagnostics + path/session loop | Expanded in branch | Partially integrated | Contract and integration parity |
630649
| L4 Interaction | agent conversation + focus/path pane runtime | Implemented in branch | M1-M4 baseline integrated on mainline | Expand capability surface via typed contract only |
631-
| L5 Governance | runbook, diagnostics, replay/autonomy controls | Expanded in branch | Operator diagnostics persistence/triage/history/threshold governance + runbook automation/audit + adaptive simulation/remediation + remediation backtest/approval-gate + approval-policy hardening/regression-alarms + approval-policy drift/escalation + escalation acknowledgement lifecycle/audit + escalation SLA/reminder baseline + notification digest/suppression baseline + delivery-log observability + stale-cleanup health auditing integrated | M7.20: anomaly surfacing and retention-policy governance |
650+
| L5 Governance | runbook, diagnostics, replay/autonomy controls | Expanded in branch | Operator diagnostics persistence/triage/history/threshold governance + runbook automation/audit + adaptive simulation/remediation + remediation backtest/approval-gate + approval-policy hardening/regression-alarms + approval-policy drift/escalation + escalation acknowledgement lifecycle/audit + escalation SLA/reminder baseline + notification digest/suppression baseline + delivery-log observability + stale-cleanup health auditing + anomaly/retention governance integrated | M7.21: notification escalation SLOs and anomaly-threshold governance |
632651

633652
## Verification Baseline
634653

docs/diataxis/zh/explanation/development-progress-dashboard.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,25 @@
582582
- `npm run test:agent-workspace:contracts`
583583
- `npm run verify:agent-workspace:runtime`
584584

585+
## 主线最新增量(2026-04-16 M7.20 通知异常浮现与 Retention Policy 治理链路)
586+
587+
- 已在 `src/server.ts` 增加通知异常路由:
588+
- `GET /api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notification-anomalies?limit=...`
589+
- 已扩展通知治理输出:
590+
- `/triage/remediation/escalation/notification-policy` 现在输出显式 `retentionPolicy`,明确 trail 上界与 digest history 上界语义。
591+
- 已新增确定性异常合成:
592+
- suppressed 数量压过 emitted 数量时输出 suppression dominance 告警,
593+
- digest throttled 事件积压时输出 throttle backlog 信号,
594+
- 发生陈旧 reminder 清理时输出 stale cleanup 信号。
595+
- 已补可执行证据:
596+
- `src/server.migration.test.ts` 新增 retention-policy 载荷与 anomaly-report 路由语义断言。
597+
- 已加固 runtime 门禁:
598+
- `src/knowledge.api.contract.test.ts``src/agent_workspace.verification.contract.test.ts``scripts/verify-agent-workspace-runtime.js` 新增 notification-anomalies 路由与 retention/anomaly helper 的 fail-fast 断言。
599+
- 验证证据:
600+
- `npm test -- src/server.migration.test.ts --runInBand --testNamePattern \"escalation notification anomaly surfacing and retention-policy governance stay deterministic\"`
601+
- `npm run test:agent-workspace:contracts`
602+
- `npm run verify:agent-workspace:runtime`
603+
585604
## 主线 vs 工作分支快照(2026-04-14)
586605

587606
| 能力切片 | 工作分支(`feat/learning-multi-tutor-adapter`| 主线(`origin/main`| 集成状态 |
@@ -630,7 +649,7 @@
630649
| L2 检索层 | 可解释混合/向量检索 + 治理 | 分支规划增强中 | 主线当前为 file-backed 基线(`src/learning/store.ts`| 待主线出现对应模块证据后再收敛 |
631650
| L3 学习层 | 掌握诊断 + 路径/会话闭环 | 分支增强中 | 主线部分集成 | 契约与集成一致性 |
632651
| L4 交互层 | agent 对话 + focus/path pane 运行时 | 分支已实现 | 主线 M1-M4 已落入基线 | 继续通过 typed contract 扩展动作面 |
633-
| L5 治理层 | runbook/诊断/回放与自动化 | 分支增强中 | 主线已集成运维诊断持久化/分级/趋势历史/阈值治理 + runbook 自动化/阈值审计 + 自适应模拟/自动修复 + 回测/批准门禁 + 批准策略硬化/回归告警 + 批准策略漂移/升级 + 升级确认生命周期/审计 + 升级 SLA/提醒基线 + 通知摘要/抑制基线 + 交付日志可观测性 + 陈旧通知健康审计 | M7.20:异常通知浮现与 retention policy 治理 |
652+
| L5 治理层 | runbook/诊断/回放与自动化 | 分支增强中 | 主线已集成运维诊断持久化/分级/趋势历史/阈值治理 + runbook 自动化/阈值审计 + 自适应模拟/自动修复 + 回测/批准门禁 + 批准策略硬化/回归告警 + 批准策略漂移/升级 + 升级确认生命周期/审计 + 升级 SLA/提醒基线 + 通知摘要/抑制基线 + 交付日志可观测性 + 陈旧通知健康审计 + 异常/retention 治理 | M7.21:通知升级 SLO 与异常阈值治理 |
634653

635654
## 验证基线
636655

scripts/verify-agent-workspace-runtime.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ function verifyAgentWorkspaceRuntime(repoRoot = path.resolve(__dirname, '..')) {
168168
serverSource.includes('/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notification-health'),
169169
'Missing diagnostics remediation escalation notification health route in src/server.ts'
170170
);
171+
assert(
172+
serverSource.includes('/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notification-anomalies'),
173+
'Missing diagnostics remediation escalation notification anomalies route in src/server.ts'
174+
);
171175
assert(
172176
serverSource.includes('/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/digest'),
173177
'Missing diagnostics remediation escalation digest route in src/server.ts'
@@ -300,6 +304,14 @@ function verifyAgentWorkspaceRuntime(repoRoot = path.resolve(__dirname, '..')) {
300304
serverSource.includes('buildAgentWorkspaceDiagnosticsRemediationEscalationNotificationHealthReport'),
301305
'Missing remediation escalation notification health helper in src/server.ts'
302306
);
307+
assert(
308+
serverSource.includes('getAgentWorkspaceDiagnosticsRemediationEscalationNotificationRetentionPolicy'),
309+
'Missing remediation escalation notification retention policy helper in src/server.ts'
310+
);
311+
assert(
312+
serverSource.includes('buildAgentWorkspaceDiagnosticsRemediationEscalationNotificationAnomalyReport'),
313+
'Missing remediation escalation notification anomaly report helper in src/server.ts'
314+
);
303315
assert(
304316
serverSource.includes('applyAgentWorkspaceDiagnosticsRemediationEscalationReminderSuppressionPolicy'),
305317
'Missing remediation escalation reminder suppression policy helper in src/server.ts'
@@ -367,14 +379,14 @@ function verifyAgentWorkspaceRuntime(repoRoot = path.resolve(__dirname, '..')) {
367379
'diagnostics remediation policy drift route exists',
368380
'diagnostics remediation escalation route exists',
369381
'diagnostics remediation escalation SLA/reminder routes exist',
370-
'diagnostics remediation escalation notification policy, log, health, and digest routes exist',
382+
'diagnostics remediation escalation notification policy, log, health, anomaly, and digest routes exist',
371383
'diagnostics remediation escalation acknowledgement routes exist',
372384
'diagnostics remediation approval routes exist',
373385
'diagnostics triage remediation route exists',
374386
'diagnostics retention governance exists',
375387
'diagnostics alert-threshold governance helpers exist',
376388
'diagnostics threshold simulation, drift, remediation, escalation, and SLA helpers exist',
377-
'diagnostics escalation notification, digest cadence, reminder suppression, delivery-log, and health helpers exist',
389+
'diagnostics escalation notification, digest cadence, reminder suppression, delivery-log, health, and anomaly helpers exist',
378390
'diagnostics remediation policy and alarm helpers exist',
379391
'diagnostics threshold audit helpers exist',
380392
'diagnostics remediation approval trail helpers exist',

src/agent_workspace.verification.contract.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ describe('agent workspace verification script contracts', () => {
6565
expect(runtimeSource).toContain('/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notification-policy');
6666
expect(runtimeSource).toContain('/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notifications');
6767
expect(runtimeSource).toContain('/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notification-health');
68+
expect(runtimeSource).toContain('/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notification-anomalies');
6869
expect(runtimeSource).toContain('/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/digest');
6970
expect(runtimeSource).toContain('/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/acknowledge');
7071
expect(runtimeSource).toContain('/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/approvals');
@@ -96,6 +97,8 @@ describe('agent workspace verification script contracts', () => {
9697
expect(runtimeSource).toContain('buildAgentWorkspaceDiagnosticsRemediationEscalationNotificationSummary');
9798
expect(runtimeSource).toContain('cleanupStaleAgentWorkspaceDiagnosticsRemediationEscalationNotificationTrail');
9899
expect(runtimeSource).toContain('buildAgentWorkspaceDiagnosticsRemediationEscalationNotificationHealthReport');
100+
expect(runtimeSource).toContain('getAgentWorkspaceDiagnosticsRemediationEscalationNotificationRetentionPolicy');
101+
expect(runtimeSource).toContain('buildAgentWorkspaceDiagnosticsRemediationEscalationNotificationAnomalyReport');
99102
expect(runtimeSource).toContain('applyAgentWorkspaceDiagnosticsRemediationEscalationReminderSuppressionPolicy');
100103
expect(runtimeSource).toContain('buildAgentWorkspaceDiagnosticsRemediationEscalationGovernanceContext');
101104
expect(runtimeSource).toContain('acknowledgeAgentWorkspaceDiagnosticsRemediationEscalation');

src/knowledge.api.contract.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ describe('Knowledge mastery API contract wiring', () => {
2929
'/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notification-policy',
3030
'/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notifications',
3131
'/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notification-health',
32+
'/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notification-anomalies',
3233
'/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/digest',
3334
'/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/acknowledge',
3435
'/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/approvals',

src/server.migration.test.ts

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2448,6 +2448,131 @@ describe('server migration settings routes', () => {
24482448
).toBe(true);
24492449
});
24502450

2451+
test('escalation notification anomaly surfacing and retention-policy governance stay deterministic', async () => {
2452+
const diagnosticsDir = path.join(runtimeDataDir, 'agent_workspace_diagnostics');
2453+
const escalationNotificationPath = path.join(diagnosticsDir, 'triage_remediation_escalation_notifications.v1.json');
2454+
await fs.promises.mkdir(diagnosticsDir, { recursive: true });
2455+
await fs.promises.writeFile(
2456+
escalationNotificationPath,
2457+
JSON.stringify(
2458+
[
2459+
{
2460+
notificationId: 'suppressed-1',
2461+
generatedAt: '2026-04-16T00:00:00.000Z',
2462+
channel: 'reminder',
2463+
status: 'suppressed',
2464+
escalationId: 'drift:approval-metadata-policy-drift',
2465+
reminderId: 'reminder:suppressed-1',
2466+
digestId: '',
2467+
severity: 'warning',
2468+
title: 'suppressed reminder',
2469+
reason: 'cooldown_active',
2470+
forceDispatch: false,
2471+
nextEligibleAt: '2026-04-16T06:00:00.000Z',
2472+
overdueHours: 5,
2473+
previousOverdueHours: 3,
2474+
currentOverdueHours: 5,
2475+
breaches: 0,
2476+
emittedReminders: 0,
2477+
suppressedReminders: 1,
2478+
runbookLinkIds: ['development-progress-dashboard']
2479+
},
2480+
{
2481+
notificationId: 'throttled-1',
2482+
generatedAt: '2026-04-16T01:00:00.000Z',
2483+
channel: 'digest',
2484+
status: 'throttled',
2485+
escalationId: '',
2486+
reminderId: '',
2487+
digestId: 'digest:throttled-1',
2488+
severity: '',
2489+
title: 'throttled digest',
2490+
reason: 'digest_cadence_active',
2491+
forceDispatch: false,
2492+
nextEligibleAt: '2026-04-16T02:00:00.000Z',
2493+
overdueHours: 0,
2494+
previousOverdueHours: 0,
2495+
currentOverdueHours: 0,
2496+
breaches: 2,
2497+
emittedReminders: 0,
2498+
suppressedReminders: 1,
2499+
runbookLinkIds: ['development-progress-dashboard']
2500+
},
2501+
{
2502+
notificationId: 'emitted-1',
2503+
generatedAt: '2026-04-15T23:00:00.000Z',
2504+
channel: 'digest',
2505+
status: 'emitted',
2506+
escalationId: '',
2507+
reminderId: '',
2508+
digestId: 'digest:emitted-1',
2509+
severity: '',
2510+
title: 'emitted digest',
2511+
reason: '',
2512+
forceDispatch: false,
2513+
nextEligibleAt: '',
2514+
overdueHours: 0,
2515+
previousOverdueHours: 0,
2516+
currentOverdueHours: 0,
2517+
breaches: 1,
2518+
emittedReminders: 1,
2519+
suppressedReminders: 0,
2520+
runbookLinkIds: ['development-progress-dashboard']
2521+
}
2522+
],
2523+
null,
2524+
2
2525+
),
2526+
'utf8'
2527+
);
2528+
2529+
const notificationPolicyResponse = await requestJson(
2530+
port,
2531+
'GET',
2532+
'/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notification-policy'
2533+
);
2534+
expect(notificationPolicyResponse.status).toBe(200);
2535+
expect(notificationPolicyResponse.body.success).toBe(true);
2536+
expect(notificationPolicyResponse.body.retentionPolicy).toEqual(
2537+
expect.objectContaining({
2538+
maxEntries: expect.any(Number),
2539+
staleReminderPolicy: 'prune_inactive_escalation',
2540+
cleanupMode: expect.any(String),
2541+
channelBounds: expect.objectContaining({
2542+
digestHistory: expect.any(Number)
2543+
})
2544+
})
2545+
);
2546+
2547+
const anomaliesResponse = await requestJson(
2548+
port,
2549+
'GET',
2550+
'/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notification-anomalies?limit=10'
2551+
);
2552+
expect(anomaliesResponse.status).toBe(200);
2553+
expect(anomaliesResponse.body.success).toBe(true);
2554+
expect(anomaliesResponse.body.report).toEqual(
2555+
expect.objectContaining({
2556+
generatedAt: expect.any(String),
2557+
anomalies: expect.any(Array),
2558+
summary: expect.objectContaining({
2559+
total: expect.any(Number),
2560+
warning: expect.any(Number),
2561+
info: expect.any(Number)
2562+
})
2563+
})
2564+
);
2565+
expect(anomaliesResponse.body.report.summary.total).toBeGreaterThan(0);
2566+
expect(anomaliesResponse.body.report.anomalies).toEqual(
2567+
expect.arrayContaining([
2568+
expect.objectContaining({
2569+
anomalyId: expect.stringMatching(/suppression_dominates_delivery|digest_throttle_backlog/),
2570+
runbookLinkIds: expect.arrayContaining(['development-progress-dashboard'])
2571+
})
2572+
])
2573+
);
2574+
});
2575+
24512576
test('server runtime path avoids synchronous filesystem APIs', () => {
24522577
const serverSourcePath = path.join(__dirname, 'server.ts');
24532578
const serverSource = fs.readFileSync(serverSourcePath, 'utf8');

0 commit comments

Comments
 (0)