feat(setup): surface operator-pack status and add operations runbook

This commit is contained in:
William Valentin
2026-02-16 15:30:38 -08:00
parent dc1c691ea5
commit 65efda3533
7 changed files with 145 additions and 6 deletions
+1
View File
@@ -770,6 +770,7 @@ Repeated failure/recovery notifications are throttled by `notify_cooldown`.
- `automation.minio_sync.notify.channel: webchat` - `automation.minio_sync.notify.channel: webchat`
`flynn setup` now includes an Operator Pack option in Automation that preconfigures scheduled backups, heartbeat alerts, a daily briefing, and a default MinIO sync task, with prompts for output channel/peer routing. `flynn setup` now includes an Operator Pack option in Automation that preconfigures scheduled backups, heartbeat alerts, a daily briefing, and a default MinIO sync task, with prompts for output channel/peer routing.
See `docs/operations/OPERATOR_PACK.md` for an operations runbook and verification checklist.
Example Operator Pack output routing: Example Operator Pack output routing:
+2
View File
@@ -19,6 +19,8 @@ This documentation is written to be useful to both humans and AI agents. If you
5. Production and performance 5. Production and performance
- `docs/deployment/PRODUCTION.md` - `docs/deployment/PRODUCTION.md`
- `docs/performance/TUNING.md` - `docs/performance/TUNING.md`
6. Operations runbooks
- `docs/operations/OPERATOR_PACK.md`
## Quick Map (One Diagram) ## Quick Map (One Diagram)
+72
View File
@@ -0,0 +1,72 @@
# Operator Pack Runbook
This runbook documents the setup and operating model for Flynn's Operator Pack.
## What It Configures
When enabled in `flynn setup` Automation, Operator Pack preconfigures:
- `backup.enabled: true` with cron schedule and output notifications.
- `automation.heartbeat.enabled: true` with output notifications.
- `automation.daily_briefing.enabled: true` with scheduled briefing output.
- `automation.minio_sync.enabled: true` with a default knowledge sync task (optional during setup).
Setup prompts for:
- Output routing: `channel` and `peer` (for backup + heartbeat + daily briefing + minio sync notifications).
- Backup cron schedule.
- Daily briefing cron schedule.
- Include/skip default MinIO sync task.
## Recommended Baseline
```yaml
backup:
enabled: true
schedule: "0 2 * * *"
notify:
channel: telegram
peer: "123456789"
automation:
heartbeat:
enabled: true
interval: "5m"
notify:
channel: telegram
peer: "123456789"
notify_cooldown: "30m"
daily_briefing:
enabled: true
schedule: "0 8 * * *"
output:
channel: telegram
peer: "123456789"
minio_sync:
enabled: true
interval: "6h"
run_on_start: true
notify:
channel: telegram
peer: "123456789"
tasks:
- prefix: "knowledge/"
namespace_base: "global/knowledge/minio"
mode: append
max_objects: 20
max_chars_per_object: 8000
force: false
```
## Verification Checklist
1. Run `flynn doctor --strict` and verify zero failures/warnings.
2. Confirm heartbeat route is valid for the configured channel/peer.
3. Confirm backup cron and daily briefing cron schedules match operator expectations.
4. If using MinIO ingestion, confirm extractor dependencies via doctor output (`MinIO ingest extractors`).
## Notes
- Heartbeat notification noise is controlled by `automation.heartbeat.notify_cooldown` (default `30m`).
- If `notify_cooldown` is invalid, Flynn falls back to `30m` and logs a warning.
- Re-running setup Automation detects an existing Operator Pack and asks whether to reconfigure.
+5 -3
View File
@@ -193,7 +193,7 @@
"status": "completed", "status": "completed",
"date": "2026-02-16", "date": "2026-02-16",
"updated": "2026-02-16", "updated": "2026-02-16",
"summary": "Implemented operator-focused hardening and onboarding polish: added setup Operator Pack flows in both Automation menu and first-run wizard to preconfigure scheduled backups, heartbeat alerts, daily briefing, and default MinIO sync; added operator-pack prompts for output channel/peer routing with redundant prompt skip when already configured; added heartbeat notification throttling via `automation.heartbeat.notify_cooldown` (with invalid-value fallback handling); and added `flynn doctor --strict` to treat warnings as failures. Added docs/changelog updates and a consolidated regression run record.", "summary": "Implemented operator-focused hardening and onboarding polish: added setup Operator Pack flows in both Automation menu and first-run wizard to preconfigure scheduled backups, heartbeat alerts, daily briefing, and default MinIO sync; added operator-pack prompts for output channel/peer routing with redundant prompt skip when already configured; added a compact Operator Pack status line in setup menu output; added heartbeat notification throttling via `automation.heartbeat.notify_cooldown` (with invalid-value fallback handling); and added `flynn doctor --strict` to treat warnings as failures. Added docs/changelog updates, including a dedicated Operator Pack operations runbook, and a consolidated regression run record.",
"files_modified": [ "files_modified": [
"CHANGELOG.md", "CHANGELOG.md",
"src/cli/setup/config.ts", "src/cli/setup/config.ts",
@@ -213,9 +213,11 @@
"src/cli/doctor.test.ts", "src/cli/doctor.test.ts",
"config/default.yaml", "config/default.yaml",
"README.md", "README.md",
"docs/README.md",
"docs/operations/OPERATOR_PACK.md",
"docs/plans/state.json" "docs/plans/state.json"
], ],
"test_status": "pnpm test:run src/cli/index.test.ts src/cli/setup/automation.test.ts src/cli/setup/integration.test.ts src/cli/setup/summary.test.ts src/automation/heartbeat.test.ts src/config/schema.test.ts src/cli/doctor.test.ts + pnpm typecheck passing" "test_status": "pnpm test:run src/cli/setup/summary.test.ts src/cli/setup/orchestrator.test.ts src/cli/setup/automation.test.ts src/cli/setup/integration.test.ts + pnpm typecheck passing"
}, },
"backup-session-summary-audit-trail": { "backup-session-summary-audit-trail": {
"status": "completed", "status": "completed",
@@ -3501,7 +3503,7 @@
} }
}, },
"overall_progress": { "overall_progress": {
"total_test_count": 1872, "total_test_count": 1874,
"all_tests_passing": true, "all_tests_passing": true,
"p0_completion": "3/3 (100%)", "p0_completion": "3/3 (100%)",
"p1_completion": "4/4 (100%)", "p1_completion": "4/4 (100%)",
+4 -2
View File
@@ -1,6 +1,6 @@
import type { Prompter } from './prompts.js'; import type { Prompter } from './prompts.js';
import type { ConfigBuilder } from './config.js'; import type { ConfigBuilder } from './config.js';
import { renderSummary } from './summary.js'; import { renderOperatorPackStatus, renderSummary } from './summary.js';
import { setupProviders } from './providers.js'; import { setupProviders } from './providers.js';
import { setupChannels } from './channels.js'; import { setupChannels } from './channels.js';
import { setupMemory } from './memory.js'; import { setupMemory } from './memory.js';
@@ -28,9 +28,11 @@ const SECTION_HANDLERS: Record<string, (p: Prompter, b: ConfigBuilder) => Promis
export async function runMenu(p: Prompter, builder: ConfigBuilder): Promise<void> { export async function runMenu(p: Prompter, builder: ConfigBuilder): Promise<void> {
while (true) { while (true) {
const config = builder.build();
p.println(); p.println();
p.println('Flynn Setup — Current Configuration'); p.println('Flynn Setup — Current Configuration');
p.println(renderSummary(builder.build())); p.println(renderSummary(config));
p.println(` ${renderOperatorPackStatus(config)}`);
p.println(); p.println();
p.println('What would you like to configure?'); p.println('What would you like to configure?');
for (let i = 0; i < MENU_OPTIONS.length; i++) { for (let i = 0; i < MENU_OPTIONS.length; i++) {
+40 -1
View File
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { renderSummary } from './summary.js'; import { renderOperatorPackStatus, renderSummary } from './summary.js';
import type { SetupConfig } from './config.js'; import type { SetupConfig } from './config.js';
describe('renderSummary', () => { describe('renderSummary', () => {
@@ -24,4 +24,43 @@ describe('renderSummary', () => {
expect(output).toContain('Automation: heartbeat, daily-briefing, minio-sync'); expect(output).toContain('Automation: heartbeat, daily-briefing, minio-sync');
expect(output).toContain('Backup: enabled (cron 0 2 * * *)'); expect(output).toContain('Backup: enabled (cron 0 2 * * *)');
}); });
it('renders compact operator pack status line with routing when enabled', () => {
const config = {
models: {
default: { provider: 'anthropic', model: 'claude-sonnet' },
},
server: { port: 18800, localhost: true },
automation: {
heartbeat: {
enabled: true,
notify: { channel: 'telegram', peer: '123' },
},
daily_briefing: { enabled: true },
},
backup: {
enabled: true,
notify: { channel: 'telegram', peer: '123' },
},
} as unknown as SetupConfig;
expect(renderOperatorPackStatus(config)).toBe('Operator Pack: enabled (telegram/123)');
});
it('renders compact operator pack status line as disabled when partial config', () => {
const config = {
models: {
default: { provider: 'anthropic', model: 'claude-sonnet' },
},
server: { port: 18800, localhost: true },
automation: {
heartbeat: { enabled: true },
},
backup: {
enabled: true,
},
} as unknown as SetupConfig;
expect(renderOperatorPackStatus(config)).toBe('Operator Pack: disabled');
});
}); });
+21
View File
@@ -1,5 +1,26 @@
import type { SetupConfig } from './config.js'; import type { SetupConfig } from './config.js';
export function renderOperatorPackStatus(config: SetupConfig): string {
const automation = config.automation ?? {};
const backup = config.backup;
const enabled = Boolean(
backup?.enabled
&& automation.heartbeat?.enabled
&& automation.daily_briefing?.enabled,
);
if (!enabled) {
return 'Operator Pack: disabled';
}
const notify = backup?.notify ?? (automation.heartbeat as { notify?: { channel?: string; peer?: string } } | undefined)?.notify;
if (notify?.channel && notify?.peer) {
return `Operator Pack: enabled (${notify.channel}/${notify.peer})`;
}
return 'Operator Pack: enabled';
}
export function renderSummary(config: SetupConfig): string { export function renderSummary(config: SetupConfig): string {
const lines: string[] = []; const lines: string[] = [];