Skip to content

Commit a211e0d

Browse files
committed
feat(agent-workspace): add notification health cleanup
1 parent fb07e2e commit a211e0d

8 files changed

Lines changed: 323 additions & 5 deletions

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,32 @@ Deliverables:
587587
- `npm run test:agent-workspace:contracts`
588588
- `npm run verify:agent-workspace:runtime`
589589

590+
### M7.19 (Now): Notification Delivery Health Auditing and Stale Cleanup (Lane Ops Bridge)
591+
592+
Deliverables:
593+
594+
- add bounded notification-health route for operator delivery auditing.
595+
- remove stale reminder notification records whose escalation is no longer active.
596+
- expose cleanup counts so retention behavior is auditable instead of implicit.
597+
598+
#### M7.19 Progress Note (2026-04-16)
599+
600+
- [Done] expanded `src/server.ts` with notification health route:
601+
- `GET /api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notification-health?limit=...`.
602+
- [Done] added deterministic stale-notification cleanup behavior:
603+
- reminder-channel delivery records are pruned when their `escalationId` no longer exists in active escalation governance state.
604+
- [Done] added bounded health helper stack:
605+
- `cleanupStaleAgentWorkspaceDiagnosticsRemediationEscalationNotificationTrail(...)`,
606+
- `buildAgentWorkspaceDiagnosticsRemediationEscalationNotificationHealthReport(...)`.
607+
- [Done] expanded evidence coverage:
608+
- `src/server.migration.test.ts` now validates notification health route semantics, cleanup counts, stale-record pruning, and persisted file cleanup.
609+
- `src/knowledge.api.contract.test.ts` now fail-fast checks notification-health route contract.
610+
- `src/agent_workspace.verification.contract.test.ts` + `scripts/verify-agent-workspace-runtime.js` now fail fast on notification-health route and cleanup-helper drift.
611+
- [Done] verification evidence:
612+
- `npm test -- src/server.migration.test.ts --runInBand --testNamePattern "escalation notification health audit and stale cleanup stay deterministic"`
613+
- `npm run test:agent-workspace:contracts`
614+
- `npm run verify:agent-workspace:runtime`
615+
590616
## Success Criteria
591617

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

597623
## Next Step
598624

599-
Proceed to `/prompts:ce-plan` using this document as the source for `M7.19` decomposition (notification delivery health auditing and stale-notification cleanup), while preserving M7 lane boundary constraints.
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.

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,24 @@ Execution anchor:
562562
- `npm run test:agent-workspace:contracts`
563563
- `npm run verify:agent-workspace:runtime`
564564

565+
## Latest Mainline Increment (2026-04-16 M7.19 Notification Delivery Health Auditing and Stale Cleanup Lane)
566+
567+
- Expanded `src/server.ts` with notification health route:
568+
- `GET /api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notification-health?limit=...`.
569+
- Added deterministic stale-notification cleanup behavior:
570+
- reminder-channel notification records are pruned when their `escalationId` no longer exists in active escalation governance state.
571+
- Added bounded delivery-health auditing helpers:
572+
- cleanup helper for stale reminder records,
573+
- health-report helper exposing active escalation count, tracked notifications, cleanup delta, and by-channel/by-status counters.
574+
- Expanded executable evidence:
575+
- `src/server.migration.test.ts` now validates notification health route semantics, stale-record removal, cleanup counts, and persisted notification-log cleanup.
576+
- Hardened runtime verification gate:
577+
- `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-health route and cleanup-helper drift.
578+
- Verification evidence:
579+
- `npm test -- src/server.migration.test.ts --runInBand --testNamePattern \"escalation notification health audit and stale cleanup stay deterministic\"`
580+
- `npm run test:agent-workspace:contracts`
581+
- `npm run verify:agent-workspace:runtime`
582+
565583
## Mainline vs Working-Branch Snapshot (2026-04-14)
566584

567585
| Capability Slice | Working Branch (`feat/learning-multi-tutor-adapter`) | Mainline (`origin/main`) | Integration Status |
@@ -610,7 +628,7 @@ This dashboard aligns against the following requirement chain:
610628
| 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 |
611629
| L3 Learning | mastery diagnostics + path/session loop | Expanded in branch | Partially integrated | Contract and integration parity |
612630
| L4 Interaction | agent conversation + focus/path pane runtime | Implemented in branch | M1-M4 baseline integrated on mainline | Expand capability surface via typed contract only |
613-
| 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 integrated | M7.19: delivery-health auditing and stale-notification cleanup |
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 |
614632

615633
## Verification Baseline
616634

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,24 @@
564564
- `npm run test:agent-workspace:contracts`
565565
- `npm run verify:agent-workspace:runtime`
566566

567+
## 主线最新增量(2026-04-16 M7.19 通知交付健康审计与陈旧通知清理链路)
568+
569+
- 已在 `src/server.ts` 增加通知健康审计路由:
570+
- `GET /api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notification-health?limit=...`
571+
- 已新增确定性陈旧通知清理行为:
572+
- reminder channel 通知记录在其 `escalationId` 不再存在于当前 active escalation 治理状态时会被清理。
573+
- 已新增有界交付健康 helper:
574+
- 陈旧 reminder 记录清理 helper,
575+
- 输出 active escalation 数、tracked notification 数、cleanup delta 与 by-channel/by-status 聚合的 health helper。
576+
- 已补可执行证据:
577+
- `src/server.migration.test.ts` 新增通知健康路由语义、陈旧记录移除、cleanup 计数与通知日志落盘清理断言。
578+
- 已加固 runtime 门禁:
579+
- `src/knowledge.api.contract.test.ts``src/agent_workspace.verification.contract.test.ts``scripts/verify-agent-workspace-runtime.js` 新增 notification-health 路由与 cleanup helper 的 fail-fast 断言。
580+
- 验证证据:
581+
- `npm test -- src/server.migration.test.ts --runInBand --testNamePattern \"escalation notification health audit and stale cleanup stay deterministic\"`
582+
- `npm run test:agent-workspace:contracts`
583+
- `npm run verify:agent-workspace:runtime`
584+
567585
## 主线 vs 工作分支快照(2026-04-14)
568586

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

617635
## 验证基线
618636

scripts/verify-agent-workspace-runtime.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ function verifyAgentWorkspaceRuntime(repoRoot = path.resolve(__dirname, '..')) {
164164
serverSource.includes('/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notifications'),
165165
'Missing diagnostics remediation escalation notifications route in src/server.ts'
166166
);
167+
assert(
168+
serverSource.includes('/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notification-health'),
169+
'Missing diagnostics remediation escalation notification health route in src/server.ts'
170+
);
167171
assert(
168172
serverSource.includes('/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/digest'),
169173
'Missing diagnostics remediation escalation digest route in src/server.ts'
@@ -288,6 +292,14 @@ function verifyAgentWorkspaceRuntime(repoRoot = path.resolve(__dirname, '..')) {
288292
serverSource.includes('buildAgentWorkspaceDiagnosticsRemediationEscalationNotificationSummary'),
289293
'Missing remediation escalation notification summary helper in src/server.ts'
290294
);
295+
assert(
296+
serverSource.includes('cleanupStaleAgentWorkspaceDiagnosticsRemediationEscalationNotificationTrail'),
297+
'Missing remediation escalation stale notification cleanup helper in src/server.ts'
298+
);
299+
assert(
300+
serverSource.includes('buildAgentWorkspaceDiagnosticsRemediationEscalationNotificationHealthReport'),
301+
'Missing remediation escalation notification health helper in src/server.ts'
302+
);
291303
assert(
292304
serverSource.includes('applyAgentWorkspaceDiagnosticsRemediationEscalationReminderSuppressionPolicy'),
293305
'Missing remediation escalation reminder suppression policy helper in src/server.ts'
@@ -355,14 +367,14 @@ function verifyAgentWorkspaceRuntime(repoRoot = path.resolve(__dirname, '..')) {
355367
'diagnostics remediation policy drift route exists',
356368
'diagnostics remediation escalation route exists',
357369
'diagnostics remediation escalation SLA/reminder routes exist',
358-
'diagnostics remediation escalation notification policy, log, and digest routes exist',
370+
'diagnostics remediation escalation notification policy, log, health, and digest routes exist',
359371
'diagnostics remediation escalation acknowledgement routes exist',
360372
'diagnostics remediation approval routes exist',
361373
'diagnostics triage remediation route exists',
362374
'diagnostics retention governance exists',
363375
'diagnostics alert-threshold governance helpers exist',
364376
'diagnostics threshold simulation, drift, remediation, escalation, and SLA helpers exist',
365-
'diagnostics escalation notification, digest cadence, reminder suppression, and delivery-log helpers exist',
377+
'diagnostics escalation notification, digest cadence, reminder suppression, delivery-log, and health helpers exist',
366378
'diagnostics remediation policy and alarm helpers exist',
367379
'diagnostics threshold audit helpers exist',
368380
'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
@@ -64,6 +64,7 @@ describe('agent workspace verification script contracts', () => {
6464
expect(runtimeSource).toContain('/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/reminders');
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');
67+
expect(runtimeSource).toContain('/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notification-health');
6768
expect(runtimeSource).toContain('/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/digest');
6869
expect(runtimeSource).toContain('/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/acknowledge');
6970
expect(runtimeSource).toContain('/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/approvals');
@@ -93,6 +94,8 @@ describe('agent workspace verification script contracts', () => {
9394
expect(runtimeSource).toContain('readAgentWorkspaceDiagnosticsRemediationEscalationNotificationTrail');
9495
expect(runtimeSource).toContain('appendAgentWorkspaceDiagnosticsRemediationEscalationNotificationEntries');
9596
expect(runtimeSource).toContain('buildAgentWorkspaceDiagnosticsRemediationEscalationNotificationSummary');
97+
expect(runtimeSource).toContain('cleanupStaleAgentWorkspaceDiagnosticsRemediationEscalationNotificationTrail');
98+
expect(runtimeSource).toContain('buildAgentWorkspaceDiagnosticsRemediationEscalationNotificationHealthReport');
9699
expect(runtimeSource).toContain('applyAgentWorkspaceDiagnosticsRemediationEscalationReminderSuppressionPolicy');
97100
expect(runtimeSource).toContain('buildAgentWorkspaceDiagnosticsRemediationEscalationGovernanceContext');
98101
expect(runtimeSource).toContain('acknowledgeAgentWorkspaceDiagnosticsRemediationEscalation');

src/knowledge.api.contract.test.ts

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

src/server.migration.test.ts

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2310,6 +2310,144 @@ describe('server migration settings routes', () => {
23102310
).toBe(true);
23112311
});
23122312

2313+
test('escalation notification health audit and stale cleanup stay deterministic', async () => {
2314+
const diagnosticsDir = path.join(runtimeDataDir, 'agent_workspace_diagnostics');
2315+
const approvalTrailPath = path.join(diagnosticsDir, 'triage_remediation_approvals.v1.json');
2316+
const escalationNotificationPath = path.join(diagnosticsDir, 'triage_remediation_escalation_notifications.v1.json');
2317+
await fs.promises.mkdir(diagnosticsDir, { recursive: true });
2318+
await fs.promises.rm(escalationNotificationPath, { force: true });
2319+
await fs.promises.writeFile(
2320+
approvalTrailPath,
2321+
JSON.stringify(
2322+
[
2323+
{
2324+
approvalId: 'awdr-m719-health-seed',
2325+
approvedAt: '2026-04-10T00:00:00.000Z',
2326+
approvedBy: 'op',
2327+
reason: 'm7.19 deterministic seed',
2328+
strategy: 'conservative',
2329+
window: 8,
2330+
expiresAt: '2020-01-01T00:00:00.000Z',
2331+
consumedAt: '',
2332+
simulation: {
2333+
generatedAt: '2026-04-10T00:00:00.000Z',
2334+
changedKeys: ['topReplayLimit'],
2335+
recommendedThresholds: {
2336+
replayRateMedium: 0.2,
2337+
replayRateHigh: 0.5,
2338+
includeFailureAsHighRisk: true,
2339+
topReplayLimit: 5,
2340+
historyWindow: 20,
2341+
source: 'seed',
2342+
updatedAt: '2026-04-10T00:00:00.000Z'
2343+
}
2344+
}
2345+
}
2346+
],
2347+
null,
2348+
2
2349+
),
2350+
'utf8'
2351+
);
2352+
await fs.promises.writeFile(
2353+
escalationNotificationPath,
2354+
JSON.stringify(
2355+
[
2356+
{
2357+
notificationId: 'stale-reminder-1',
2358+
generatedAt: '2020-01-01T00:00:00.000Z',
2359+
channel: 'reminder',
2360+
status: 'emitted',
2361+
escalationId: 'stale:resolved-escalation',
2362+
reminderId: 'reminder:stale:resolved-escalation',
2363+
digestId: '',
2364+
severity: 'warning',
2365+
title: 'stale reminder',
2366+
reason: '',
2367+
forceDispatch: false,
2368+
nextEligibleAt: '',
2369+
overdueHours: 4,
2370+
previousOverdueHours: 0,
2371+
currentOverdueHours: 4,
2372+
breaches: 0,
2373+
emittedReminders: 1,
2374+
suppressedReminders: 0,
2375+
runbookLinkIds: ['development-progress-dashboard']
2376+
},
2377+
{
2378+
notificationId: 'recent-digest-1',
2379+
generatedAt: '2026-04-16T00:00:00.000Z',
2380+
channel: 'digest',
2381+
status: 'emitted',
2382+
escalationId: '',
2383+
reminderId: '',
2384+
digestId: 'digest:recent',
2385+
severity: '',
2386+
title: 'recent digest',
2387+
reason: '',
2388+
forceDispatch: false,
2389+
nextEligibleAt: '',
2390+
overdueHours: 0,
2391+
previousOverdueHours: 0,
2392+
currentOverdueHours: 0,
2393+
breaches: 2,
2394+
emittedReminders: 1,
2395+
suppressedReminders: 0,
2396+
runbookLinkIds: ['development-progress-dashboard']
2397+
}
2398+
],
2399+
null,
2400+
2
2401+
),
2402+
'utf8'
2403+
);
2404+
2405+
const healthResponse = await requestJson(
2406+
port,
2407+
'GET',
2408+
'/api/knowledge/operator/agent-workspace-diagnostics/triage/remediation/escalation/notification-health?limit=10'
2409+
);
2410+
expect(healthResponse.status).toBe(200);
2411+
expect(healthResponse.body.success).toBe(true);
2412+
expect(healthResponse.body.cleanup).toEqual(
2413+
expect.objectContaining({
2414+
removed: expect.any(Number),
2415+
retained: expect.any(Number)
2416+
})
2417+
);
2418+
expect(healthResponse.body.cleanup.removed).toBeGreaterThan(0);
2419+
expect(healthResponse.body.health).toEqual(
2420+
expect.objectContaining({
2421+
activeEscalations: expect.any(Number),
2422+
trackedNotifications: expect.any(Number),
2423+
staleNotificationsRemoved: expect.any(Number),
2424+
byChannel: expect.objectContaining({
2425+
reminder: expect.any(Number),
2426+
digest: expect.any(Number)
2427+
}),
2428+
byStatus: expect.objectContaining({
2429+
emitted: expect.any(Number),
2430+
suppressed: expect.any(Number),
2431+
throttled: expect.any(Number)
2432+
})
2433+
})
2434+
);
2435+
expect(healthResponse.body.health.staleNotificationsRemoved).toBeGreaterThan(0);
2436+
expect(healthResponse.body.notifications).toEqual(expect.any(Array));
2437+
expect(
2438+
healthResponse.body.notifications.some((entry: any) => entry.notificationId === 'stale-reminder-1')
2439+
).toBe(false);
2440+
2441+
const persistedNotificationTrail = JSON.parse(await fs.promises.readFile(escalationNotificationPath, 'utf8'));
2442+
expect(Array.isArray(persistedNotificationTrail)).toBe(true);
2443+
expect(
2444+
persistedNotificationTrail.some((entry: any) => entry.notificationId === 'stale-reminder-1')
2445+
).toBe(false);
2446+
expect(
2447+
persistedNotificationTrail.some((entry: any) => entry.notificationId === 'recent-digest-1')
2448+
).toBe(true);
2449+
});
2450+
23132451
test('server runtime path avoids synchronous filesystem APIs', () => {
23142452
const serverSourcePath = path.join(__dirname, 'server.ts');
23152453
const serverSource = fs.readFileSync(serverSourcePath, 'utf8');

0 commit comments

Comments
 (0)