From d8119ce0d98b23043c53e65f5a28ea046b22c180 Mon Sep 17 00:00:00 2001 From: gabemontero Date: Wed, 8 Jul 2026 12:16:24 -0400 Subject: [PATCH 01/19] chore(boost): RHDHPLAN-1513 openspec content for operations & upstream alignment Create OpenSpec changes covering all 4 epics and 13 stories under RHDHPLAN-1513 (AI Catalog Ingestion Operations & Upstream Schema Alignment): - ingestion-health-dashboard (RHIDP-15331): 5 stories, 3 specs, 7 files Health status API, admin health UI, error classification - connector-config-hot-reload (RHIDP-15332): 3 stories, 3 specs, 7 files Zod config schemas, RuntimeConfigResolver hot-reload, config admin UI - ingestion-audit-metrics (RHIDP-15333): 3 stories, 3 specs, 7 files Sync audit events, analytics REST API, Eval Hub integration - upstream-schema-alignment (RHIDP-15334): 2 stories, 2 specs, 6 files Annotation specification document, dry-run migration-readiness tooling Cross-references RHDHPLAN-1507 (entity model), RHDHPLAN-1508 (audit logging pattern), RHDHPLAN-1510 (connectors), and upstream Backstage RFCs #32062 and #33060. Co-Authored-By: Claude Opus 4.6 Signed-off-by: gabemontero --- .../.openspec.yaml | 3 + .../connector-config-hot-reload/design.md | 139 ++++++++++++ .../connector-config-hot-reload/proposal.md | 42 ++++ .../specs/config-admin-ui/spec.md | 204 +++++++++++++++++ .../specs/config-schemas/spec.md | 115 ++++++++++ .../specs/hot-reload-propagation/spec.md | 148 ++++++++++++ .../connector-config-hot-reload/tasks.md | 99 ++++++++ .../ingestion-audit-metrics/.openspec.yaml | 3 + .../changes/ingestion-audit-metrics/design.md | 211 ++++++++++++++++++ .../ingestion-audit-metrics/proposal.md | 56 +++++ .../specs/analytics-api/spec.md | 74 ++++++ .../specs/audit-events/spec.md | 71 ++++++ .../specs/eval-hub-integration/spec.md | 84 +++++++ .../changes/ingestion-audit-metrics/tasks.md | 174 +++++++++++++++ .../ingestion-health-dashboard/.openspec.yaml | 3 + .../ingestion-health-dashboard/design.md | 121 ++++++++++ .../ingestion-health-dashboard/proposal.md | 59 +++++ .../specs/admin-health-ui/spec.md | 166 ++++++++++++++ .../specs/error-classification/spec.md | 154 +++++++++++++ .../specs/health-status-api/spec.md | 135 +++++++++++ .../ingestion-health-dashboard/tasks.md | 121 ++++++++++ .../upstream-schema-alignment/.openspec.yaml | 3 + .../upstream-schema-alignment/design.md | 105 +++++++++ .../upstream-schema-alignment/proposal.md | 56 +++++ .../specs/annotation-specification/spec.md | 111 +++++++++ .../specs/migration-readiness-tooling/spec.md | 136 +++++++++++ .../upstream-schema-alignment/tasks.md | 63 ++++++ 27 files changed, 2656 insertions(+) create mode 100644 workspaces/boost/openspec/changes/connector-config-hot-reload/.openspec.yaml create mode 100644 workspaces/boost/openspec/changes/connector-config-hot-reload/design.md create mode 100644 workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md create mode 100644 workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md create mode 100644 workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md create mode 100644 workspaces/boost/openspec/changes/connector-config-hot-reload/specs/hot-reload-propagation/spec.md create mode 100644 workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md create mode 100644 workspaces/boost/openspec/changes/ingestion-audit-metrics/.openspec.yaml create mode 100644 workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md create mode 100644 workspaces/boost/openspec/changes/ingestion-audit-metrics/proposal.md create mode 100644 workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/analytics-api/spec.md create mode 100644 workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/audit-events/spec.md create mode 100644 workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/eval-hub-integration/spec.md create mode 100644 workspaces/boost/openspec/changes/ingestion-audit-metrics/tasks.md create mode 100644 workspaces/boost/openspec/changes/ingestion-health-dashboard/.openspec.yaml create mode 100644 workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md create mode 100644 workspaces/boost/openspec/changes/ingestion-health-dashboard/proposal.md create mode 100644 workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/admin-health-ui/spec.md create mode 100644 workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/error-classification/spec.md create mode 100644 workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/health-status-api/spec.md create mode 100644 workspaces/boost/openspec/changes/ingestion-health-dashboard/tasks.md create mode 100644 workspaces/boost/openspec/changes/upstream-schema-alignment/.openspec.yaml create mode 100644 workspaces/boost/openspec/changes/upstream-schema-alignment/design.md create mode 100644 workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md create mode 100644 workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md create mode 100644 workspaces/boost/openspec/changes/upstream-schema-alignment/specs/migration-readiness-tooling/spec.md create mode 100644 workspaces/boost/openspec/changes/upstream-schema-alignment/tasks.md diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/.openspec.yaml b/workspaces/boost/openspec/changes/connector-config-hot-reload/.openspec.yaml new file mode 100644 index 00000000000..9df4af2b637 --- /dev/null +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/.openspec.yaml @@ -0,0 +1,3 @@ +schema: spec-driven +created: 2026-07-08 +status: draft diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md new file mode 100644 index 00000000000..da8a56226b2 --- /dev/null +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md @@ -0,0 +1,139 @@ +# Design: Connector Configuration Hot-Reload + +## Context + +Boost's `RuntimeConfigResolver` provides a two-layer config model: YAML baseline (read-only, deployment-time) + database overrides (mutable, runtime). The resolver caches the merged config with 30-second TTL and immediate invalidation on DB writes. This change extends that proven infrastructure to connector configuration. + +Backstage's built-in `ConfigApi` loads YAML at startup with no hot-reload. Boost's `RuntimeConfigResolver` is a **custom layer** built on top of `ConfigApi` that adds: + +- Database-backed overrides (via `AdminConfigService`) +- TTL-based refresh (30s cache with immediate invalidation) +- Merged resolution (YAML baseline + DB overrides) + +Connector config becomes a new scope under this existing infrastructure. + +## Goals + +- Extend `RuntimeConfigResolver` to connector settings, not create a new config system +- Hot-reload enable/disable, endpoint URL, and schedule changes within 30s +- Preserve deployment-time config (TLS mount paths, K8s Secret references) as YAML-only +- Admin UI for connector config with RBAC gating +- Handle K8s Secret mount propagation delays (up to 60s for projected volumes) + +## Non-Goals + +- Creating a new config system — reuse `RuntimeConfigResolver` +- Replacing Backstage's `ConfigApi` — extend it with a custom layer +- Hot-reloading YAML files — YAML is the baseline layer, only DB overrides are hot-reloadable +- Hot-reloading TLS certificates or mount paths — these are deployment-time config +- Event-driven propagation — polling-based via existing reconciliation cycles is sufficient + +## Decisions + +### Decision 1: Extend RuntimeConfigResolver, don't create new config system + +Connector config extends Boost's existing `RuntimeConfigResolver` two-layer model (YAML baseline + DB overrides + 30s TTL). Connectors read config through the resolver, get hot-reload for free. + +This reuses proven infrastructure (tested in production for core boost settings) instead of building a parallel config system. The resolver already handles: + +- Two-layer merging (YAML baseline + DB overrides) +- Cache invalidation (30s TTL + immediate invalidation on write) +- Schema validation (Zod schemas reject invalid values) + +**Implementation pattern:** + +```typescript +// Connector entity provider reads config via RuntimeConfigResolver +const connectorConfig = + await runtimeConfigResolver.getConfig('connectors.jira'); +if (!connectorConfig.enabled) { + this.logger.info('Jira connector disabled via runtime config, skipping sync'); + return; +} + +const endpoint = + connectorConfig.endpoint || + this.config.getString('boost.connectors.jira.endpoint'); +await this.syncClient.connect(endpoint); +``` + +**Why not a separate system:** Creating a new config layer would duplicate cache invalidation, TTL logic, DB schema, admin API, and frontend UI patterns — all of which `RuntimeConfigResolver` already provides. + +### Decision 2: configScope annotation strategy + +Each connector config field is annotated with `configScope` to control which layer it lives in: + +| Field | configScope | Rationale | +| ----------------------- | ---------------- | ----------------------------------------------------------------------------------------------------- | +| `enabled` | `db-overridable` | Admin can toggle without YAML change | +| `endpoint` | `db-overridable` | Admin can switch environments without deployment | +| `schedule.intervalMs` | `db-overridable` | Admin can tune sync frequency at runtime | +| `tls.caFile` | `yaml-only` | Mount path can't change at runtime (requires pod restart) | +| `credentials.secretRef` | `yaml-only` | K8s Secret references are deployment-time config | +| `credentials.secretKey` | `yaml-only` | K8s Secret key names are deployment-time config | +| `schedule.cron` | `db-overridable` | Admin can change cron schedule at runtime | +| `namespace` | `yaml-only` | Namespace is deployment-time config (can't change active provider's target namespace without restart) | +| `batchSize` | `db-overridable` | Admin can tune performance at runtime | +| `timeout.connectionMs` | `db-overridable` | Admin can adjust for network conditions at runtime | + +**Why not make everything db-overridable:** Mount paths and Secret references can't change at runtime without a pod restart. Making them `db-overridable` would create false expectations of hot-reload capability. + +**Why not make everything yaml-only:** Enable/disable and endpoint changes are the primary use case for hot-reload. Requiring YAML edits defeats the purpose. + +### Decision 3: Propagation mechanism — polling-based via reconciliation cycles + +Entity providers receive config changes on next reconciliation cycle. Provider's scheduled task reads config from `RuntimeConfigResolver` at each cycle start. No event/callback needed — polling-based via existing schedule. + +**Why polling, not events:** Connectors already run on scheduled reconciliation cycles (e.g., every 5 minutes for Jira). Reading config at cycle start is zero marginal cost — the provider is already waking up. Event-driven propagation would add complexity (event bus, handler registration, failure recovery) for no latency benefit (30s TTL + schedule interval ≤ 5m30s, which is acceptable for config changes). + +**Propagation sequence:** + +1. Admin saves DB override via admin UI (e.g., disables Jira connector) +2. `AdminConfigService` writes to DB, invalidates cache +3. Next reconciliation cycle (within schedule interval, e.g., ≤5m) +4. Provider reads config via `RuntimeConfigResolver` +5. Resolver cache miss (invalidated), fetches fresh YAML + DB overrides +6. Provider sees `enabled: false`, skips sync + +**Worst-case latency:** Cache TTL (30s) + reconciliation interval (e.g., 5m) = 5m30s. For critical changes (incident response), admin can reduce the connector's schedule interval temporarily via DB override. + +### Decision 4: Credential rotation handling + +K8s Secret mount propagation can take up to 60s for projected volumes (kubelet sync period). Provider re-reads mounted file each cycle. Effective credential rotation = mount propagation delay (≤60s) + resolver TTL (30s) = ≤90s worst case. + +**Why not watch Secret mounts:** Filesystem watching (inotify) adds complexity and failure modes (missed events, watcher exhaustion). Reading the mounted Secret file at each reconciliation cycle is simpler and sufficient for credential rotation use cases. + +**Credential rotation sequence:** + +1. Admin updates K8s Secret content (e.g., new API token) +2. Kubelet syncs projected volume (up to 60s delay) +3. Next reconciliation cycle (within schedule interval) +4. Provider reads mounted Secret file +5. Provider uses new credentials for sync + +**Why not immediate propagation:** Credentials are long-lived secrets (API tokens, certificates). 90s rotation latency is acceptable. Immediate propagation would require filesystem watchers or polling loops, adding complexity for minimal benefit. + +### Decision 5: Admin UI writes DB overrides via AdminConfigService + +Admin UI writes connector config changes via `AdminConfigService` — same pattern as existing boost admin settings. Zod schema validates before write. Frontend uses existing admin API patterns. + +**Why not a separate connector config API:** Reusing `AdminConfigService` maintains consistency with other runtime-overridable settings (AI provider config, feature flags, etc.). The admin panel already has RBAC gating, audit logging, and validation patterns. + +**UI flow:** + +1. Admin opens connector config section +2. Form fields pre-populated with current merged config (YAML baseline + DB overrides) +3. Admin toggles `enabled` or changes `endpoint` +4. Frontend calls `POST /admin/config/connectors.jira` with new values +5. Backend validates via Zod schema, writes DB override, invalidates cache +6. Frontend shows immediate visual feedback ("Saved — will take effect within 30s") + +**YAML-only fields (read-only in UI):** TLS mount paths, Secret references shown as read-only info. Tooltip: "Deployment-time config. Edit YAML to change." + +**Why immediate visual feedback:** Admin sees "Saved" immediately, understands propagation delay. Prevents confusion about "did my change take effect?" + +## Risks + +- **K8s Secret mount propagation delays:** Mitigated by documenting 90s worst-case credential rotation latency. For emergency credential rotation, admin can manually restart connector pod. +- **Config schema versioning:** Mitigated by Zod schema versioning. Breaking changes require migration logic in `RuntimeConfigResolver`. +- **Partial config state during cache refresh:** Mitigated by atomic cache updates. Resolver fetches full merged config (YAML + DB) before updating cache entry. diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md new file mode 100644 index 00000000000..cb55a47cced --- /dev/null +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md @@ -0,0 +1,42 @@ +# Proposal: Connector Configuration Hot-Reload + +## Why + +Connectors need configuration changes without pod restart. Toggling a connector on/off, changing sync schedules, or updating endpoint URLs currently requires editing YAML config and redeploying — a multi-minute cycle that blocks quick experimentation and rapid incident response. Enterprise customers need immediate control over ingestion behavior without downtime. + +Boost's existing `RuntimeConfigResolver` already solves this for core boost settings: it provides a two-layer config model (YAML baseline + database overrides) with 30-second TTL refresh. This change extends that proven infrastructure to connector settings, enabling hot-reload of connector enable/disable, endpoint URLs, and sync schedules. + +The key distinction: Backstage's built-in `ConfigApi` loads config at startup with no hot-reload capability. Boost's `RuntimeConfigResolver` is a **custom layer** that adds DB overrides and TTL-based refresh on top of the YAML baseline. This epic extends that custom layer to connector configuration. + +## What Boost Builds + +### Config Schemas + +- Zod schema definitions for per-connector settings: `enabled`, `endpoint`, `schedule`, `tls`, `credentials` +- `configScope` annotation on each field: `yaml-only` (mount paths, K8s Secret references), `db-overridable` (enable/disable, endpoint URL, schedule), `db-only` (runtime-only state) +- Schema validation rejects invalid connector config values before write +- Integration with `RuntimeConfigResolver`'s two-layer resolution + +### Hot-Reload Propagation + +- Runtime overrides propagate to active entity provider instances within 30s TTL +- Connector responds to enable/disable changes on next reconciliation cycle +- Schedule changes take effect on next reconciliation cycle +- Endpoint URL changes take effect on next sync cycle +- K8s Secret mount propagation delays (up to 60s for projected volumes) handled transparently + +### Config Admin UI + +- Admin UI section for toggling connectors on/off +- Endpoint URL and sync schedule configuration fields +- K8s Secret reference field (display only — Secret names are deployment-time config) +- Changes saved via `AdminConfigService` DB overrides +- Takes effect via `RuntimeConfigResolver` hot-reload pattern +- RBAC gating: admin-only access to connector config + +## Impact + +- `RuntimeConfigResolver` extension — connector config becomes a new scope under the two-layer model +- Admin panel — new connector config section +- Connector modules — entity providers read config through `RuntimeConfigResolver`, get hot-reload for free +- No changes to Backstage upstream — this is a Boost-specific custom config layer diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md new file mode 100644 index 00000000000..570df09d85d --- /dev/null +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md @@ -0,0 +1,204 @@ +# Config Admin UI + +> **Status: Draft** — Pre-implementation specification. Subject to change during implementation. + +Admin UI section for toggling connectors, setting endpoints/schedules, referencing K8s Secrets. Changes saved via `AdminConfigService` DB overrides. Takes effect via `RuntimeConfigResolver` hot-reload pattern. RBAC gating: admin-only access. + +## EXISTING Requirements + +### Requirement: Connector Toggle UI + +Admin UI provides toggle controls for enabling/disabling connectors. + +#### Scenario: Connector toggle list + +- **WHEN** admin opens connector config section +- **THEN** UI displays list of all configured connectors (Jira, GitHub, GitLab) +- **AND** each connector has a toggle switch showing current `enabled` state +- **AND** toggle state reflects merged config (YAML baseline + DB override) + +#### Scenario: Toggle connector off + +- **WHEN** admin clicks toggle to disable Jira connector +- **THEN** frontend calls `POST /admin/config/connectors.jira` with `{ enabled: false }` +- **AND** backend validates via Zod schema, writes DB override, invalidates cache +- **AND** frontend shows immediate visual feedback: "Saved — will take effect within 30 seconds" +- **AND** toggle UI updates to show "Disabled" state + +#### Scenario: Toggle connector on + +- **WHEN** admin clicks toggle to enable previously disabled Jira connector +- **THEN** frontend calls `POST /admin/config/connectors.jira` with `{ enabled: true }` +- **AND** backend writes DB override, invalidates cache +- **AND** frontend shows immediate visual feedback +- **AND** toggle UI updates to show "Enabled" state + +### Requirement: Endpoint and Schedule Form Fields + +Admin UI provides form fields for endpoint URL and sync schedule configuration. + +#### Scenario: Endpoint URL field + +- **WHEN** admin opens Jira connector config detail view +- **THEN** UI displays input field for `endpoint` pre-populated with current merged config value +- **AND** field includes validation indicator (URL format check) +- **AND** field shows help text: "Jira instance URL (e.g., https://jira.example.com)" + +#### Scenario: Change endpoint URL + +- **WHEN** admin edits endpoint URL to `https://jira-staging.example.com` and saves +- **THEN** frontend validates URL format before submitting +- **AND** frontend calls `POST /admin/config/connectors.jira` with `{ endpoint: "https://jira-staging.example.com" }` +- **AND** backend validates, writes DB override, invalidates cache +- **AND** UI shows: "Saved — new endpoint will be used on next sync (within schedule interval)" + +#### Scenario: Schedule interval field + +- **WHEN** admin opens connector config detail view +- **THEN** UI displays input field for `schedule.intervalMs` with human-readable conversion (e.g., "5 minutes" displayed as 300000 ms) +- **AND** field includes slider or dropdown for common intervals: 1m, 5m, 10m, 30m, 1h, 6h, 12h, 24h + +#### Scenario: Change schedule interval + +- **WHEN** admin changes schedule from "5 minutes" to "10 minutes" and saves +- **THEN** frontend calls `POST /admin/config/connectors.jira` with `{ schedule: { intervalMs: 600000 } }` +- **AND** backend validates, writes DB override, invalidates cache +- **AND** UI shows: "Saved — new schedule will take effect on next cycle" + +#### Scenario: Cron schedule field + +- **WHEN** admin switches from interval to cron schedule +- **THEN** UI displays cron expression input field with validation +- **AND** UI provides cron builder helper (dropdowns for hour, day of week, etc.) +- **AND** field shows example: "0 _/2 _ \* \* = every 2 hours" + +### Requirement: K8s Secret Reference Field + +Admin UI displays K8s Secret references as read-only info (deployment-time config). + +#### Scenario: Secret reference displayed as read-only + +- **WHEN** admin opens connector config detail view +- **THEN** UI displays `credentials.secretRef` and `credentials.secretKey` as read-only text fields +- **AND** fields show current YAML baseline values (e.g., `secretRef: "jira-credentials"`, `secretKey: "api-token"`) +- **AND** fields include tooltip: "Deployment-time config. Edit YAML to change. See [docs] for credential rotation." + +#### Scenario: Secret reference cannot be edited via UI + +- **WHEN** admin attempts to edit `credentials.secretRef` field +- **THEN** UI field is disabled (grayed out, no cursor) +- **AND** tooltip explains: "Secret references cannot be changed at runtime. Update app-config.yaml and redeploy." + +### Requirement: DB Override Save via AdminConfigService + +Admin UI saves connector config changes via `AdminConfigService` with schema validation. + +#### Scenario: Save endpoint change via AdminConfigService + +- **WHEN** admin changes endpoint and clicks "Save" +- **THEN** frontend calls `POST /api/boost/admin/config` with payload: + ```json + { + "key": "connectors.jira", + "value": { "endpoint": "https://jira-staging.example.com" } + } + ``` +- **AND** backend `AdminConfigService.setConfig()` validates via Jira connector Zod schema +- **AND** backend writes DB override to `admin_config` table +- **AND** backend calls `RuntimeConfigResolver.invalidate('connectors.jira')` +- **AND** backend returns success response +- **AND** frontend displays success notification + +#### Scenario: Save rejected by schema validation + +- **WHEN** admin enters invalid endpoint URL "not-a-url" and clicks "Save" +- **THEN** frontend calls `POST /api/boost/admin/config` +- **AND** backend validates via Zod schema, rejects with error: "Invalid URL format for endpoint" +- **AND** backend returns 400 error response +- **AND** frontend displays validation error below field: "Invalid URL format" + +### Requirement: Validation Feedback + +Admin UI provides immediate validation feedback before and after save. + +#### Scenario: Client-side validation before save + +- **WHEN** admin enters invalid endpoint URL +- **THEN** UI field shows red border and error icon +- **AND** UI displays validation message: "Please enter a valid URL" +- **AND** "Save" button is disabled until validation passes + +#### Scenario: Server-side validation error feedback + +- **WHEN** admin saves config and server rejects with validation error +- **THEN** UI displays error notification at top of form: "Failed to save: [error message]" +- **AND** field that caused error is highlighted +- **AND** focus returns to invalid field + +#### Scenario: Successful save confirmation + +- **WHEN** admin saves valid config change +- **THEN** UI displays success notification: "Connector config saved successfully" +- **AND** notification includes propagation info: "Changes will take effect within 30 seconds" +- **AND** notification auto-dismisses after 5 seconds + +### Requirement: RBAC Gating + +Admin UI connector config section requires admin role (RBAC permission check). + +#### Scenario: Admin user accesses connector config + +- **WHEN** user with `boost.admin` permission opens connector config section +- **THEN** UI displays full connector config form with edit capabilities +- **AND** all fields (except yaml-only) are editable +- **AND** "Save" button is enabled + +#### Scenario: Non-admin user blocked from connector config + +- **WHEN** user without `boost.admin` permission attempts to access connector config section +- **THEN** UI displays permission error: "You do not have permission to configure connectors" +- **AND** config section is not rendered +- **AND** user is redirected to unauthorized page + +#### Scenario: Non-admin user sees read-only connector status + +- **WHEN** connector config section is configured to allow read-only access for non-admin users +- **THEN** UI displays connector list with read-only toggle states and settings +- **AND** all form fields are disabled +- **AND** "Save" button is hidden +- **AND** UI shows info banner: "Read-only view. Contact administrator to change connector settings." + +### Requirement: Immediate Visual Feedback + +Admin UI provides immediate visual feedback on save without waiting for propagation. + +#### Scenario: Optimistic UI update on save + +- **WHEN** admin saves connector config change +- **THEN** UI immediately updates displayed values to reflect new config (before backend response) +- **AND** UI shows loading spinner during save request +- **AND** UI reverts to old values if save fails + +#### Scenario: Propagation delay communication + +- **WHEN** admin saves connector config change +- **THEN** success notification includes: "Saved — will take effect within 30 seconds (cache TTL) + next reconciliation cycle" +- **AND** UI provides link to connector health dashboard to monitor effect + +#### Scenario: Config change history + +- **WHEN** admin views connector config section +- **THEN** UI displays recent config change history (last 10 changes) +- **AND** history includes: timestamp, changed fields, old/new values, user who made change +- **AND** history is sourced from audit log (via `AdminConfigService`) + +## ADDED Requirements + +### Requirement: Specification Coverage + +This capability area MUST have its existing behavior documented as baseline acceptance criteria. + +#### Scenario: Baseline validation + +- **WHEN** the existing implementation is validated against this specification +- **THEN** all scenarios described in the EXISTING Requirements section MUST pass diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md new file mode 100644 index 00000000000..f5af37c1660 --- /dev/null +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md @@ -0,0 +1,115 @@ +# Config Schemas + +> **Status: Draft** — Pre-implementation specification. Subject to change during implementation. + +Zod schemas define per-connector configuration fields with `configScope` annotations. `RuntimeConfigResolver` integrates schemas for two-layer resolution (YAML baseline + DB overrides). Schema validation rejects invalid values before write. + +## EXISTING Requirements + +### Requirement: Zod Schema Definition Per Connector + +Each connector has a Zod schema defining all configuration fields with `configScope` annotations. + +#### Scenario: Jira connector config schema + +- **WHEN** Jira connector config schema is defined +- **THEN** schema includes fields: `enabled` (boolean), `endpoint` (URL string), `schedule.intervalMs` (number), `schedule.cron` (string), `credentials.secretRef` (string), `credentials.secretKey` (string), `namespace` (string), `batchSize` (number), `timeout.connectionMs` (number) +- **AND** each field is annotated with `configScope`: `enabled` is `db-overridable`, `endpoint` is `db-overridable`, `schedule.intervalMs` is `db-overridable`, `schedule.cron` is `db-overridable`, `credentials.secretRef` is `yaml-only`, `credentials.secretKey` is `yaml-only`, `namespace` is `yaml-only`, `batchSize` is `db-overridable`, `timeout.connectionMs` is `db-overridable` + +#### Scenario: GitHub connector config schema + +- **WHEN** GitHub connector config schema is defined +- **THEN** schema includes fields: `enabled` (boolean), `endpoint` (URL string), `schedule.intervalMs` (number), `credentials.secretRef` (string), `credentials.secretKey` (string), `namespace` (string), `batchSize` (number) +- **AND** `configScope` annotations match Jira pattern: `enabled`, `endpoint`, `schedule.intervalMs`, `batchSize` are `db-overridable`; `credentials.*`, `namespace` are `yaml-only` + +#### Scenario: GitLab connector config schema + +- **WHEN** GitLab connector config schema is defined +- **THEN** schema includes fields: `enabled` (boolean), `endpoint` (URL string), `schedule.intervalMs` (number), `credentials.secretRef` (string), `credentials.secretKey` (string), `namespace` (string), `batchSize` (number) +- **AND** `configScope` annotations match Jira pattern + +### Requirement: RuntimeConfigResolver Integration + +`RuntimeConfigResolver` uses connector Zod schemas for two-layer config resolution. + +#### Scenario: Two-layer resolution with schema validation + +- **WHEN** `RuntimeConfigResolver.getConfig('connectors.jira')` is called +- **THEN** resolver reads YAML baseline from `ConfigApi` at `boost.connectors.jira` +- **AND** resolver reads DB overrides from `AdminConfigService` for key `connectors.jira` +- **AND** resolver merges YAML + DB, validating against Jira connector Zod schema +- **AND** resolver returns merged config object with 30s TTL cache + +#### Scenario: DB override takes precedence over YAML + +- **WHEN** YAML config has `enabled: true` and DB override has `enabled: false` +- **THEN** `RuntimeConfigResolver.getConfig('connectors.jira')` returns `enabled: false` + +#### Scenario: YAML-only field rejects DB override + +- **WHEN** admin attempts to write DB override for `credentials.secretRef` +- **THEN** schema validation rejects the write because `credentials.secretRef` has `configScope: yaml-only` +- **AND** admin receives error: "Field credentials.secretRef is yaml-only and cannot be overridden at runtime" + +### Requirement: Validation Rejection + +Schema validation rejects invalid connector config values before write. + +#### Scenario: Invalid endpoint URL rejected + +- **WHEN** admin attempts to write DB override with `endpoint: "not-a-url"` +- **THEN** Zod schema validation rejects the write +- **AND** admin receives error: "Invalid URL format for endpoint" + +#### Scenario: Negative schedule interval rejected + +- **WHEN** admin attempts to write DB override with `schedule.intervalMs: -1000` +- **THEN** Zod schema validation rejects the write +- **AND** admin receives error: "schedule.intervalMs must be a positive number" + +#### Scenario: Invalid cron expression rejected + +- **WHEN** admin attempts to write DB override with `schedule.cron: "not-a-cron"` +- **THEN** Zod schema validation rejects the write (via cron parser) +- **AND** admin receives error: "Invalid cron expression" + +### Requirement: Schema Versioning + +Connector config schemas support versioning for backward compatibility. + +#### Scenario: Schema version stored with DB override + +- **WHEN** admin writes DB override for `connectors.jira` +- **THEN** DB entry includes schema version (e.g., `schemaVersion: 1`) +- **AND** future reads validate against matching schema version + +#### Scenario: Schema migration on version mismatch + +- **WHEN** DB override has `schemaVersion: 1` and current schema is `schemaVersion: 2` +- **THEN** `RuntimeConfigResolver` applies migration logic to upgrade old config +- **AND** migrated config validates against current schema + +### Requirement: Default Values + +Connector config schemas define default values for optional fields. + +#### Scenario: Default schedule interval + +- **WHEN** connector config omits `schedule.intervalMs` +- **THEN** schema provides default value (e.g., `300000` ms = 5 minutes) + +#### Scenario: Default batch size + +- **WHEN** connector config omits `batchSize` +- **THEN** schema provides default value (e.g., `100`) + +## ADDED Requirements + +### Requirement: Specification Coverage + +This capability area MUST have its existing behavior documented as baseline acceptance criteria. + +#### Scenario: Baseline validation + +- **WHEN** the existing implementation is validated against this specification +- **THEN** all scenarios described in the EXISTING Requirements section MUST pass diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/hot-reload-propagation/spec.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/hot-reload-propagation/spec.md new file mode 100644 index 00000000000..76b1a3470d8 --- /dev/null +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/hot-reload-propagation/spec.md @@ -0,0 +1,148 @@ +# Hot-Reload Propagation + +> **Status: Draft** — Pre-implementation specification. Subject to change during implementation. + +Runtime overrides propagate to active connector instances within 30s TTL. Connector responds to config changes on next reconciliation cycle. Endpoint URL changes, schedule changes, and enable/disable toggles take effect without pod restart. + +## EXISTING Requirements + +### Requirement: Enable/Disable Propagation + +Enable/disable changes propagate to active entity provider within 30s + reconciliation interval. + +#### Scenario: Disable connector via DB override + +- **WHEN** admin writes DB override `connectors.jira.enabled: false` +- **THEN** `RuntimeConfigResolver` cache is invalidated immediately +- **AND** next entity provider reconciliation cycle (within 5 minutes) reads config +- **AND** provider sees `enabled: false`, logs "Jira connector disabled via runtime config, skipping sync" +- **AND** provider skips sync, no entities ingested +- **AND** total propagation time ≤ 30s TTL + 5m reconciliation interval = 5m30s + +#### Scenario: Re-enable connector via DB override + +- **WHEN** admin writes DB override `connectors.jira.enabled: true` after previously disabling +- **THEN** next reconciliation cycle reads config via `RuntimeConfigResolver` +- **AND** provider sees `enabled: true`, resumes normal sync +- **AND** entities ingested as usual + +#### Scenario: Enable/disable with no DB override (YAML baseline only) + +- **WHEN** no DB override exists for `connectors.jira.enabled` +- **THEN** `RuntimeConfigResolver` returns YAML baseline value (e.g., `enabled: true`) +- **AND** provider uses YAML baseline + +### Requirement: Schedule Change Propagation + +Schedule changes take effect on next reconciliation cycle. + +#### Scenario: Increase schedule interval via DB override + +- **WHEN** admin writes DB override `connectors.jira.schedule.intervalMs: 600000` (10 minutes) +- **THEN** current reconciliation cycle completes using old schedule (5 minutes) +- **AND** next reconciliation cycle reads new config via `RuntimeConfigResolver` +- **AND** provider reschedules task with new interval (10 minutes) +- **AND** subsequent cycles run every 10 minutes + +#### Scenario: Decrease schedule interval via DB override + +- **WHEN** admin writes DB override `connectors.jira.schedule.intervalMs: 60000` (1 minute) +- **THEN** next reconciliation cycle reads new config +- **AND** provider reschedules task with new interval (1 minute) +- **AND** subsequent cycles run every 1 minute + +#### Scenario: Switch from interval to cron schedule + +- **WHEN** admin writes DB override `connectors.jira.schedule.cron: "0 */2 * * *"` (every 2 hours) and removes `schedule.intervalMs` +- **THEN** next reconciliation cycle reads new config +- **AND** provider switches from interval-based to cron-based scheduling +- **AND** subsequent cycles run at cron-specified times + +### Requirement: Endpoint URL Change Propagation + +Endpoint URL changes take effect on next sync cycle. + +#### Scenario: Change Jira endpoint URL via DB override + +- **WHEN** admin writes DB override `connectors.jira.endpoint: "https://jira-staging.example.com"` +- **THEN** current sync cycle completes using old endpoint +- **AND** next sync cycle reads new config via `RuntimeConfigResolver` +- **AND** provider connects to new endpoint `https://jira-staging.example.com` +- **AND** entities synced from new endpoint + +#### Scenario: Invalid endpoint URL rejected before propagation + +- **WHEN** admin attempts to write DB override `connectors.jira.endpoint: "not-a-url"` +- **THEN** Zod schema validation rejects the write before cache invalidation +- **AND** no cache invalidation occurs, provider continues using old endpoint + +### Requirement: Credential Rotation Timing + +K8s Secret mount propagation delays (up to 60s) are handled transparently. Provider re-reads mounted file each cycle. + +#### Scenario: Credential rotation with Secret mount delay + +- **WHEN** admin updates K8s Secret `jira-credentials` content (new API token) +- **THEN** kubelet syncs projected volume within 60s +- **AND** next reconciliation cycle (within schedule interval, e.g., 5m) +- **AND** provider reads mounted Secret file at `/etc/boost/secrets/jira-api-token` +- **AND** provider uses new credentials for sync +- **AND** total credential rotation latency ≤ 60s kubelet delay + 5m reconciliation interval = ~6 minutes + +#### Scenario: Immediate credential use on file change + +- **WHEN** mounted Secret file changes (detected by file mtime) +- **THEN** provider reads new file content at next reconciliation cycle start +- **AND** provider uses new credentials immediately (no additional cache invalidation needed) + +### Requirement: DB Override Cache Invalidation + +Database override writes trigger immediate cache invalidation. + +#### Scenario: Cache invalidated on DB write + +- **WHEN** admin writes DB override via `AdminConfigService` +- **THEN** `AdminConfigService` calls `RuntimeConfigResolver.invalidate('connectors.jira')` +- **AND** cache entry for `connectors.jira` is deleted +- **AND** next `RuntimeConfigResolver.getConfig('connectors.jira')` call fetches fresh YAML + DB overrides + +#### Scenario: TTL-based cache refresh + +- **WHEN** no DB override write occurs within 30s +- **THEN** `RuntimeConfigResolver` cache entry expires after 30s TTL +- **AND** next `getConfig` call fetches fresh YAML + DB overrides +- **AND** ensures eventual consistency even if invalidation signal is missed + +### Requirement: Provider Behavior During Config Transition + +Entity provider handles config changes gracefully without data loss or duplicate ingestion. + +#### Scenario: Provider respects disable during in-flight sync + +- **WHEN** sync cycle is in progress and admin disables connector +- **THEN** current sync cycle completes normally (uses config snapshot from cycle start) +- **AND** next sync cycle reads new config, sees `enabled: false`, skips sync + +#### Scenario: Provider handles endpoint change without duplicate entities + +- **WHEN** endpoint URL changes mid-sync +- **THEN** current sync completes using old endpoint +- **AND** next sync uses new endpoint +- **AND** entity provider deduplication (via entity ref) prevents duplicate catalog entries + +#### Scenario: Provider logs config changes + +- **WHEN** provider detects config change (e.g., endpoint URL changed) +- **THEN** provider logs: "Connector config changed: endpoint updated from https://jira-prod.example.com to https://jira-staging.example.com" +- **AND** log includes old and new values for auditability + +## ADDED Requirements + +### Requirement: Specification Coverage + +This capability area MUST have its existing behavior documented as baseline acceptance criteria. + +#### Scenario: Baseline validation + +- **WHEN** the existing implementation is validated against this specification +- **THEN** all scenarios described in the EXISTING Requirements section MUST pass diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md new file mode 100644 index 00000000000..8ac4b1daaef --- /dev/null +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md @@ -0,0 +1,99 @@ +# Tasks: Connector Configuration Hot-Reload + +## 1. Zod Schema Definitions (P0) — RHIDP-15340 + +- [ ] 1.1 Define Jira connector config Zod schema with fields: `enabled` (boolean), `endpoint` (URL), `schedule.intervalMs` (number), `schedule.cron` (string), `credentials.secretRef` (string), `credentials.secretKey` (string), `namespace` (string), `batchSize` (number), `timeout.connectionMs` (number) +- [ ] 1.2 Annotate each Jira config field with `configScope`: `enabled`, `endpoint`, `schedule.*`, `batchSize`, `timeout.*` → `db-overridable`; `credentials.*`, `namespace` → `yaml-only` +- [ ] 1.3 Define GitHub connector config Zod schema with same field pattern +- [ ] 1.4 Define GitLab connector config Zod schema with same field pattern +- [ ] 1.5 Add URL validation for `endpoint` field (must be valid https:// URL) +- [ ] 1.6 Add positive number validation for `schedule.intervalMs`, `batchSize`, `timeout.connectionMs` +- [ ] 1.7 Add cron expression validation for `schedule.cron` (via cron parser library) +- [ ] 1.8 Define default values in schemas: `schedule.intervalMs: 300000` (5 min), `batchSize: 100`, `timeout.connectionMs: 30000` +- [ ] 1.9 Add schema versioning field: `schemaVersion: 1` in each schema +- [ ] 1.10 Add unit tests for schema validation (valid configs pass, invalid configs rejected with correct error messages) + +## 2. RuntimeConfigResolver Extension (P0) — RHIDP-15340 + +- [ ] 2.1 Extend `RuntimeConfigResolver` to support connector config scope (e.g., `connectors.jira`, `connectors.github`) +- [ ] 2.2 Implement `getConfig(key: string)` method for connector config keys +- [ ] 2.3 Implement two-layer merge: YAML baseline from `ConfigApi` + DB overrides from `AdminConfigService` +- [ ] 2.4 Implement cache with 30s TTL for merged connector config +- [ ] 2.5 Implement immediate cache invalidation on DB override write +- [ ] 2.6 Add Zod schema validation during merge (reject invalid values before caching) +- [ ] 2.7 Implement `configScope` enforcement: reject DB override writes for `yaml-only` fields +- [ ] 2.8 Add schema version migration logic for backward compatibility +- [ ] 2.9 Add unit tests for two-layer merge (YAML + DB override precedence, cache TTL, invalidation) +- [ ] 2.10 Add integration tests for `RuntimeConfigResolver` with connector schemas + +## 3. Hot-Reload Propagation (P0) — RHIDP-15341 + +- [ ] 3.1 Update Jira entity provider to read config via `RuntimeConfigResolver.getConfig('connectors.jira')` at reconciliation cycle start +- [ ] 3.2 Implement enable/disable check: skip sync if `enabled: false` +- [ ] 3.3 Implement endpoint URL propagation: use DB override endpoint if present, else YAML baseline +- [ ] 3.4 Implement schedule change propagation: reschedule task with new `schedule.intervalMs` or `schedule.cron` from merged config +- [ ] 3.5 Update GitHub entity provider with same hot-reload pattern +- [ ] 3.6 Update GitLab entity provider with same hot-reload pattern +- [ ] 3.7 Add config change logging: log old → new values when config changes detected +- [ ] 3.8 Implement credential re-read: provider reads mounted Secret file at each reconciliation cycle start +- [ ] 3.9 Add integration tests: disable connector via DB override, verify next cycle skips sync +- [ ] 3.10 Add integration tests: change endpoint via DB override, verify next cycle uses new endpoint +- [ ] 3.11 Add integration tests: change schedule via DB override, verify task rescheduled +- [ ] 3.12 Document propagation latency: 30s TTL + reconciliation interval (e.g., 5m30s for 5m interval) + +## 4. Config Admin UI (P1) — RHIDP-15342 + +- [ ] 4.1 Create connector config section in admin panel (`/admin/connectors`) +- [ ] 4.2 Implement connector list view with toggle switches for `enabled` state +- [ ] 4.3 Implement connector detail view with form fields: `endpoint` (URL input), `schedule.intervalMs` (duration picker), `schedule.cron` (cron builder) +- [ ] 4.4 Display K8s Secret references as read-only fields with tooltip: "Deployment-time config. Edit YAML to change." +- [ ] 4.5 Implement client-side validation: URL format for `endpoint`, positive numbers for `schedule.intervalMs`, cron syntax for `schedule.cron` +- [ ] 4.6 Implement save handler: call `POST /api/boost/admin/config` with connector key and updated fields +- [ ] 4.7 Implement success notification: "Saved — will take effect within 30 seconds + next reconciliation cycle" +- [ ] 4.8 Implement validation error feedback: display server-side Zod validation errors inline +- [ ] 4.9 Implement RBAC gating: require `boost.admin` permission to access connector config section +- [ ] 4.10 Implement read-only view for non-admin users (if configured) +- [ ] 4.11 Implement config change history view: display last 10 changes from audit log (timestamp, fields, old/new values, user) +- [ ] 4.12 Add UI tests: toggle connector, verify POST request with correct payload +- [ ] 4.13 Add UI tests: change endpoint, verify validation and save flow + +## 5. Credential Rotation Testing (P1) — RHIDP-15341 + +- [ ] 5.1 Add integration test: update K8s Secret content, wait for kubelet sync (≤60s), verify provider uses new credentials on next cycle +- [ ] 5.2 Document credential rotation latency: kubelet delay (≤60s) + reconciliation interval = ~6 minutes worst case +- [ ] 5.3 Add manual test procedure: emergency credential rotation with pod restart (for sub-60s latency) + +## 6. AdminConfigService Integration (P1) + +- [ ] 6.1 Add connector config endpoints to `AdminConfigService` backend API: `GET /api/boost/admin/config/connectors/:connectorId`, `POST /api/boost/admin/config/connectors/:connectorId` +- [ ] 6.2 Implement Zod schema validation in `setConfig()` method before DB write +- [ ] 6.3 Implement `configScope` enforcement: reject writes for `yaml-only` fields +- [ ] 6.4 Implement cache invalidation call to `RuntimeConfigResolver.invalidate()` after DB write +- [ ] 6.5 Add audit logging for connector config changes (timestamp, user, changed fields, old/new values) +- [ ] 6.6 Add unit tests for `AdminConfigService` connector config methods + +## 7. Testing (P1) + +- [ ] 7.1 Add unit tests for all Zod schemas (valid configs pass, invalid configs rejected) +- [ ] 7.2 Add unit tests for `RuntimeConfigResolver` connector config methods (merge, cache, invalidation) +- [ ] 7.3 Add integration tests for hot-reload propagation (enable/disable, endpoint change, schedule change) +- [ ] 7.4 Add integration tests for credential rotation (K8s Secret update, provider re-read) +- [ ] 7.5 Add UI tests for admin panel (toggle, form validation, save flow) +- [ ] 7.6 Add E2E test: admin disables connector via UI, verify next sync cycle skips ingestion +- [ ] 7.7 Add E2E test: admin changes endpoint via UI, verify next sync uses new endpoint + +## 8. Documentation (P2) + +- [ ] 8.1 Document `RuntimeConfigResolver` extension for connector config in architecture docs +- [ ] 8.2 Document `configScope` annotations and their meaning (`yaml-only`, `db-overridable`, `db-only`) +- [ ] 8.3 Document connector config admin UI usage (how to toggle, change endpoint/schedule) +- [ ] 8.4 Document propagation latency: 30s TTL + reconciliation interval +- [ ] 8.5 Document credential rotation workflow and latency (≤60s kubelet + reconciliation interval) +- [ ] 8.6 Add troubleshooting guide: "Config change not taking effect?" → check cache TTL, reconciliation schedule +- [ ] 8.7 Document RBAC permissions required for connector config access (`boost.admin`) + +## 9. Schema Migration (P2) + +- [ ] 9.1 Implement schema version migration logic in `RuntimeConfigResolver` (upgrade old config to current schema) +- [ ] 9.2 Add unit tests for schema migration (v1 → v2 config upgrade) +- [ ] 9.3 Document schema versioning and migration process for future connector config changes diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/.openspec.yaml b/workspaces/boost/openspec/changes/ingestion-audit-metrics/.openspec.yaml new file mode 100644 index 00000000000..9df4af2b637 --- /dev/null +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/.openspec.yaml @@ -0,0 +1,3 @@ +schema: spec-driven +created: 2026-07-08 +status: draft diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md new file mode 100644 index 00000000000..31620b93f6d --- /dev/null +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md @@ -0,0 +1,211 @@ +# Design: Ingestion Audit Logging and Metrics + +## Context + +Boost audit logging extends the pattern established by RHIDP-15277 (RBAC audit events). That epic defined the RHDH audit log integration, structured JSON event schema, and event emission infrastructure. This change applies the same pattern to ingestion operations. + +Analytics REST API provides the data layer for the Admin Panel's Analytics tab (RHDHPLAN-1509). The tab consumes sync history, quality scores, and match coverage metrics via RBAC-gated endpoints. + +Eval Hub is the quality feedback pipeline — external eval frameworks (LightEval, IBM Clear, GuideLLM) produce per-skill quality scores, which flow into Boost's analytics API. The API contract and availability of these eval backends are the main integration risk — not the Backstage framework. + +## Goals + +- Extend RBAC audit logging pattern (RHIDP-15277) to ingestion events from day one +- All ingestion sync attempts and config changes audited with structured events +- Analytics REST API RBAC-gated with `ai-catalog.admin` permission +- Quality score storage decoupled from eval framework specifics +- Graceful degradation when Eval Hub unavailable +- On-demand computation for expensive metrics (match coverage, aggregate distributions) + +## Non-Goals + +- Building the Analytics tab UI (covered in RHDHPLAN-1509) +- Implementing the Eval Hub pipeline or eval frameworks (Feature 8) +- Real-time metrics streaming or WebSocket push +- Prometheus/Grafana integration (may come later) +- Audit log retention policies (handled by RHDH platform) + +## Decisions + +### Decision 1: Shared audit event infrastructure with RHIDP-15277 + +Ingestion audit events follow the same structured JSON format, event schema, and log channel as RBAC audit events (RHIDP-15277). + +**Why:** Consistent audit trail across all Boost operations. Admins read one audit log channel, not multiple. Event correlation is simple when all events share the same metadata structure. + +**How to apply:** + +Event types: + +- `ingestion.sync.start` — sync attempt initiated +- `ingestion.sync.success` — sync completed successfully +- `ingestion.sync.failure` — sync failed with error details +- `ingestion.config.change` — connector configuration changed + +Event payload schema (extends RBAC audit event base schema): + +```typescript +interface IngestionAuditEvent { + type: + | 'ingestion.sync.start' + | 'ingestion.sync.success' + | 'ingestion.sync.failure' + | 'ingestion.config.change'; + timestamp: string; // ISO 8601 + actor?: string; // user or service account identifier (for config changes) + connector_name: string; // connector entity ref or name + outcome?: 'success' | 'failure'; // for sync events + assets?: { + added: number; + updated: number; + removed: number; + }; // for sync success events + error?: { + message: string; + code?: string; + }; // for sync failure events + config_change?: { + field: string; + before: string; + after: string; + }; // for config change events + metadata: Record; // extensible for connector-specific data +} +``` + +Audit log channel: RHDH audit log (same as RBAC events). Events persist to local audit log in disconnected clusters. + +**Implementation:** `plugins/boost-backend/src/audit/ingestion-events.ts` defines event types and emission helpers. Connector lifecycle hooks (`onSyncStart`, `onSyncSuccess`, `onSyncFailure`) emit audit events. Config change service emits `ingestion.config.change` events. + +### Decision 2: Analytics REST API design + +Analytics endpoints serve the Admin Panel Analytics tab (RHDHPLAN-1509). All endpoints RBAC-gated with `ai-catalog.admin` permission. + +**Why:** Centralized analytics data layer. Frontend consumes clean JSON payloads without direct DB access. RBAC gating ensures only admins see sensitive sync/quality data. + +**How to apply:** + +Endpoints: + +1. **Sync History:** + - `GET /api/boost/admin/analytics/sync-history` + - Query params: `connector_name` (optional filter), `start_date`, `end_date`, `page`, `limit` + - Response: array of sync attempts with `timestamp`, `outcome`, `assets` counts, `duration` + - Pagination via `page`/`limit`, total count in response metadata + +2. **Quality Scores:** + - `GET /api/boost/admin/analytics/quality-scores` + - Query params: `skill_entity_ref` (optional filter), `eval_source` (optional filter), `start_date`, `end_date` + - Response: per-skill quality scores array + aggregate distribution (histogram of score ranges) + - Aggregate distribution computed on-demand from DB + +3. **Match Coverage:** + - `GET /api/boost/admin/analytics/match-coverage` + - Response: `{ total_capabilities: number, matched_capabilities: number, coverage_ratio: number, unmatched_capabilities: string[] }` + - Computed on-demand by comparing agent capability declarations against skill catalog entities + +4. **Neo4j Sync Status:** + - Embedded in analytics responses (not a separate endpoint) + - Status: `{ neo4j_connected: boolean, last_sync: string, entity_count: number }` + +**Implementation:** `plugins/boost-backend/src/api/admin/analytics/routes.ts` defines Express routes. Each route checks `ai-catalog.admin` permission via `authorize` helper. Routes call service layer (`AnalyticsService`) which queries DB and computes metrics. + +### Decision 3: Quality score storage + +Per-skill quality scores stored in DB table. Aggregate distribution computed on-demand when analytics API is called. + +**Why:** Asynchronous score ingestion from Eval Hub. Scores arrive in batches, not real-time. DB storage decouples score ingestion from API serving. On-demand computation avoids continuous re-aggregation overhead. + +**How to apply:** + +DB table schema: + +```sql +CREATE TABLE boost_quality_scores ( + id SERIAL PRIMARY KEY, + skill_entity_ref VARCHAR(255) NOT NULL, + eval_source VARCHAR(100) NOT NULL, -- 'lighteval', 'ibm-clear', 'guidellm', etc. + score DECIMAL(5, 3) NOT NULL, -- 0.000 to 1.000 (normalized) + timestamp TIMESTAMP NOT NULL DEFAULT NOW(), + metadata JSONB, -- extensible for eval-framework-specific data + INDEX idx_skill_ref (skill_entity_ref), + INDEX idx_timestamp (timestamp DESC) +); +``` + +Aggregate distribution: Computed on-demand when `GET /api/boost/admin/analytics/quality-scores` is called. Query groups scores into buckets (e.g., 0.0-0.2, 0.2-0.4, ..., 0.8-1.0) and returns histogram. + +**Implementation:** `plugins/boost-backend/src/db/migrations/` adds table schema. `plugins/boost-backend/src/db/quality-scores-store.ts` provides CRUD operations. `AnalyticsService.getQualityScores()` queries DB and computes distribution. + +### Decision 4: Eval Hub as pluggable integration + +Eval Hub is a configurable external service. Generic quality score ingestion interface, not hardcoded to specific eval frameworks. + +**Why:** Eval Hub API availability and contract stability are the main risk. If Eval Hub isn't ready, Boost must still function. Pluggable design allows manual score import or mock scores during development. + +**How to apply:** + +app-config configuration: + +```yaml +boost: + evalHub: + endpoint: 'https://eval-hub.example.com/api' + enabled: true # set to false to disable Eval Hub integration + refreshInterval: '1h' # how often to poll for new scores +``` + +Eval Hub client interface: + +```typescript +interface EvalHubClient { + fetchQualityScores(): Promise; +} + +interface QualityScoreResult { + skill_entity_ref: string; + eval_source: string; // 'lighteval', 'ibm-clear', 'guidellm' + score: number; // 0.0 to 1.0 + timestamp: string; // ISO 8601 + metadata?: Record; +} +``` + +Graceful degradation: + +- If `boost.evalHub.enabled` is `false`, skip score ingestion +- If Eval Hub endpoint unreachable, log warning and continue (no crash) +- If Eval Hub returns empty scores, store empty result (don't fail) + +**Implementation:** `plugins/boost-backend/src/eval-hub/client.ts` implements `EvalHubClient`. `plugins/boost-backend/src/eval-hub/ingestion-service.ts` polls Eval Hub on interval (via background task), fetches scores, stores in DB. Config loaded from `boost.evalHub` in app-config. + +### Decision 5: Match coverage calculation + +Match coverage compares declared agent capabilities (from Kagenti agent specs) against available skills in catalog. Coverage = matched capabilities / total capabilities. Computed on-demand when analytics API is called. + +**Why:** Coverage is expensive to compute (requires fetching all agents and all skills, then matching). On-demand computation avoids continuous re-calculation. Coverage changes infrequently (only when agents or skills are added/removed). + +**How to apply:** + +Agent capabilities: Declared in Kagenti agent specs (e.g., `metadata.capabilities: ['code-review', 'bug-detection']`). Boost reads agent entities from catalog. + +Skill catalog: Entities with `spec.type: ai-skill` (from RHDHPLAN-1506 skill ingestion). Boost reads skill entities from catalog. + +Match logic: + +1. Fetch all agent entities (filter by `spec.type: ai-agent` or similar) +2. Fetch all skill entities (filter by `spec.type: ai-skill`) +3. Extract capabilities from agents: `agent.metadata.capabilities` +4. Extract skill IDs from skills: `skill.metadata.id` +5. Compute matched capabilities: capabilities that have a corresponding skill +6. Coverage ratio = matched capabilities / total capabilities +7. Unmatched capabilities = capabilities without corresponding skills + +**Implementation:** `plugins/boost-backend/src/analytics/match-coverage-calculator.ts` implements match logic. `AnalyticsService.getMatchCoverage()` calls calculator, returns coverage data. Catalog queries use Backstage catalog client. + +## Risks + +- **Eval Hub API instability:** If Eval Hub API changes frequently, integration breaks. Mitigated by pluggable design and graceful degradation (fall back to manual score import). +- **Audit log volume:** High-frequency sync operations generate many audit events. Mitigated by RHDH audit log retention policies (managed by platform, not Boost). +- **Match coverage computation cost:** Fetching all agents and skills is expensive. Mitigated by on-demand computation (only when API is called), not continuous re-calculation. Future optimization: cache coverage result for 5 minutes. +- **Quality score normalization:** Different eval frameworks produce scores on different scales (0-100, 0-1, letter grades). Mitigated by normalizing all scores to 0.0-1.0 range in `EvalHubClient.fetchQualityScores()` before storing. diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/proposal.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/proposal.md new file mode 100644 index 00000000000..7b25a7ce3e4 --- /dev/null +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/proposal.md @@ -0,0 +1,56 @@ +# Proposal: Ingestion Audit Logging and Metrics + +## Why + +Compliance requires an audit trail of ingestion operations. Every sync attempt, every config change, and every quality score must be recorded with timestamps, actors, and outcomes. The Analytics tab needs historical sync data, quality distributions, and match coverage metrics to give admins visibility into the AI system's health and evolution. + +Eval Hub integration closes the quality feedback loop — scores from LightEval, IBM Clear, and GuideLLM flow into the analytics API and surface in the admin UI. Without this loop, quality is invisible; with it, teams can track quality degradation over time and debug skill performance regressions. + +Boost builds this as RHDH-native audit logging and RBAC-gated REST endpoints from the start. Audit events follow the same structured JSON format and log channel established by RHIDP-15277 (RBAC audit logging) — ingestion events extend the existing pattern. Analytics metrics serve the Admin Panel's Analytics tab (RHDHPLAN-1509) and expose data for external observability platforms. + +## What Boost Builds + +### Sync Audit Events + +- Structured audit events for every sync attempt: `ingestion.sync.start`, `ingestion.sync.success`, `ingestion.sync.failure` +- Event payload includes: connector name, timestamp, outcome, assets added/updated/removed counts, error details +- Config change events: `ingestion.config.change` with actor, timestamp, before/after values +- Events flow into the same RHDH audit log channel as RBAC audit events (RHIDP-15277) +- Disconnected cluster support: events persist to local audit log when central RHDH is unreachable + +### Analytics REST API + +- `/api/boost/admin/analytics/sync-history` — per-connector timeline of sync attempts with outcomes +- `/api/boost/admin/analytics/quality-scores` — per-skill quality scores and aggregate distribution +- `/api/boost/admin/analytics/match-coverage` — agent capabilities vs. available skills coverage ratio +- Neo4j sync status embedded in analytics responses +- All endpoints RBAC-gated with `ai-catalog.admin` permission +- Pagination for large result sets, date range filtering + +### Eval Hub Integration + +- Configurable Eval Hub endpoint: `boost.evalHub.endpoint` in app-config +- Quality score ingestion from eval pipeline (LightEval, IBM Clear, GuideLLM as orchestration backends) +- Per-skill quality score storage in DB table: `skill_entity_ref`, `eval_source`, `score`, `timestamp` +- Aggregate quality distribution computed on-demand when analytics API is called +- Graceful handling when Eval Hub unavailable: fall back to manual score import or skip +- Asynchronous score refresh cycle — no real-time scoring + +### Key Design Principles + +- **Shared audit infrastructure** — extend RHIDP-15277 pattern, same structured JSON format, same audit log channel +- **RBAC-gated analytics** — all endpoints require `ai-catalog.admin` permission +- **Pluggable eval backends** — generic quality score ingestion interface, not hardcoded to specific eval frameworks +- **On-demand computation** — match coverage calculated when API is called, not continuously +- **Asynchronous ingestion** — Eval Hub scores arrive in batches, stored in DB, surfaced via analytics API + +## Impact + +- `plugins/boost-backend/src/audit/` — audit event infrastructure, event types, RHDH audit log integration +- `plugins/boost-backend/src/ingestion/` — sync audit event emission in connector lifecycle hooks +- `plugins/boost-backend/src/api/admin/analytics/` — REST API routes for sync history, quality scores, match coverage +- `plugins/boost-backend/src/db/` — quality score storage table, schema migrations +- `plugins/boost-backend/src/eval-hub/` — Eval Hub client, quality score ingestion service +- `plugins/boost-backend/src/analytics/` — match coverage calculator, aggregate distribution calculator +- `plugins/boost-common/src/permissions.ts` — `ai-catalog.admin` permission definition (if not already defined) +- `app-config.yaml` — `boost.evalHub.endpoint` configuration diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/analytics-api/spec.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/analytics-api/spec.md new file mode 100644 index 00000000000..d3d90414b4e --- /dev/null +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/analytics-api/spec.md @@ -0,0 +1,74 @@ +# Spec: Analytics Metrics REST API + +> **Status:** ✅ READY +> **Story:** RHIDP-15344 +> **Coverage:** Sync history endpoint, quality scores endpoint, match coverage endpoint, Neo4j sync status, RBAC gating, pagination, date range filtering + +## Scenarios + +### Scenario 1: Sync history endpoint returns per-connector timeline + +**GIVEN** a connector has completed multiple sync operations over the past week +**WHEN** an admin calls `GET /api/boost/admin/analytics/sync-history?connector_name=github-connector` +**THEN** the response contains an array of sync attempts with `timestamp`, `outcome`, `assets` counts, `duration` +**AND** the sync attempts are ordered by `timestamp` descending (most recent first) +**AND** each sync attempt includes `connector_name`, `timestamp`, `outcome` (success/failure), `assets: { added, updated, removed }` +**AND** the response includes pagination metadata: `{ page, limit, total_count }` + +### Scenario 2: Quality scores endpoint returns per-skill and aggregate + +**GIVEN** multiple skills have quality scores stored in the DB from Eval Hub ingestion +**WHEN** an admin calls `GET /api/boost/admin/analytics/quality-scores` +**THEN** the response contains an array of per-skill quality scores with `skill_entity_ref`, `eval_source`, `score`, `timestamp` +**AND** the response includes an aggregate distribution: `{ score_ranges: [{ range: '0.0-0.2', count: N }, ...] }` +**AND** the aggregate distribution is computed on-demand from the DB +**AND** scores are normalized to 0.0-1.0 range regardless of eval source + +### Scenario 3: Match coverage endpoint returns capability coverage ratio + +**GIVEN** agents have declared capabilities and skills are available in the catalog +**WHEN** an admin calls `GET /api/boost/admin/analytics/match-coverage` +**THEN** the response contains `{ total_capabilities: N, matched_capabilities: M, coverage_ratio: M/N, unmatched_capabilities: [...] }` +**AND** the coverage ratio is computed by comparing agent capabilities against skill catalog entities +**AND** unmatched capabilities are listed as an array of capability IDs +**AND** the computation is performed on-demand when the API is called + +### Scenario 4: Neo4j sync status in analytics + +**GIVEN** Neo4j is configured and syncing (or not syncing) +**WHEN** an admin calls any analytics endpoint (e.g., sync history, quality scores) +**THEN** the response includes a `neo4j_status` object: `{ connected: boolean, last_sync: string, entity_count: number }` +**AND** `connected` reflects the current Neo4j connection status +**AND** `last_sync` is the timestamp of the last successful Neo4j sync +**AND** `entity_count` is the count of entities in Neo4j (if connected) + +### Scenario 5: RBAC gating on all endpoints + +**GIVEN** a user without `ai-catalog.admin` permission +**WHEN** the user calls `GET /api/boost/admin/analytics/sync-history` +**THEN** the response is `403 Forbidden` +**AND** the error message indicates missing permission +**AND** no analytics data is leaked in the error response + +**GIVEN** a user with `ai-catalog.admin` permission +**WHEN** the user calls `GET /api/boost/admin/analytics/sync-history` +**THEN** the response is `200 OK` with analytics data + +### Scenario 6: Pagination for large result sets + +**GIVEN** a connector has 1000+ sync attempts in the DB +**WHEN** an admin calls `GET /api/boost/admin/analytics/sync-history?page=1&limit=50` +**THEN** the response contains 50 sync attempts (page 1) +**AND** the response includes pagination metadata: `{ page: 1, limit: 50, total_count: 1000+ }` + +**WHEN** the admin calls `GET /api/boost/admin/analytics/sync-history?page=2&limit=50` +**THEN** the response contains the next 50 sync attempts (page 2) +**AND** the pagination metadata reflects page 2 + +### Scenario 7: Date range filtering + +**GIVEN** a connector has sync attempts spanning 6 months +**WHEN** an admin calls `GET /api/boost/admin/analytics/sync-history?start_date=2026-01-01&end_date=2026-01-31` +**THEN** the response contains only sync attempts within the specified date range (January 2026) +**AND** sync attempts outside the date range are excluded +**AND** the response includes the applied date range in metadata: `{ filters: { start_date, end_date } }` diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/audit-events/spec.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/audit-events/spec.md new file mode 100644 index 00000000000..5fdd75bb0ab --- /dev/null +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/audit-events/spec.md @@ -0,0 +1,71 @@ +# Spec: Ingestion Sync Audit Events + +> **Status:** ✅ READY +> **Story:** RHIDP-15343 +> **Coverage:** Audit event emission for sync lifecycle, config changes, RHDH audit log integration, disconnected cluster support + +## Scenarios + +### Scenario 1: Sync start event emission + +**GIVEN** a connector is about to perform a sync operation +**WHEN** the sync lifecycle begins +**THEN** an `ingestion.sync.start` audit event is emitted with `connector_name` and `timestamp` +**AND** the event is written to the RHDH audit log channel +**AND** the event payload includes `type: 'ingestion.sync.start'` + +### Scenario 2: Sync success event with asset counts + +**GIVEN** a connector completes a sync operation successfully +**WHEN** the sync lifecycle ends with success +**THEN** an `ingestion.sync.success` audit event is emitted with `connector_name`, `timestamp`, `outcome: 'success'` +**AND** the event payload includes `assets: { added: N, updated: M, removed: K }` +**AND** the event is written to the RHDH audit log channel +**AND** asset counts reflect the actual entities added, updated, and removed during the sync + +### Scenario 3: Sync failure event with error details + +**GIVEN** a connector sync operation fails with an error +**WHEN** the sync lifecycle ends with failure +**THEN** an `ingestion.sync.failure` audit event is emitted with `connector_name`, `timestamp`, `outcome: 'failure'` +**AND** the event payload includes `error: { message: string, code?: string }` +**AND** the error message contains the failure reason (e.g., 'API timeout', 'Authentication failed') +**AND** the error code (if available) is included in the event +**AND** the event is written to the RHDH audit log channel + +### Scenario 4: Config change event with actor and before/after values + +**GIVEN** an admin changes a connector configuration field via the admin panel +**WHEN** the configuration change is committed +**THEN** an `ingestion.config.change` audit event is emitted with `connector_name`, `timestamp`, `actor` +**AND** the event payload includes `config_change: { field: string, before: string, after: string }` +**AND** the `actor` field contains the user identifier (username or service account ID) +**AND** the `before` and `after` values contain the old and new configuration values (serialized as strings) +**AND** the event is written to the RHDH audit log channel + +### Scenario 5: Audit event format consistency with RHIDP-15277 + +**GIVEN** RBAC audit events (RHIDP-15277) already emit structured JSON events +**WHEN** an ingestion audit event is emitted +**THEN** the event follows the same base schema as RBAC audit events +**AND** the event has top-level fields: `type`, `timestamp`, `metadata` +**AND** the event is serializable to JSON without loss of information +**AND** the event schema is compatible with the RHDH audit log ingestion pipeline + +### Scenario 6: Audit events in RHDH audit log channel + +**GIVEN** RHDH audit log is configured (via platform settings) +**WHEN** an ingestion audit event is emitted +**THEN** the event is written to the RHDH audit log channel (same channel as RBAC events) +**AND** the event is persisted according to RHDH platform retention policies +**AND** the event is queryable via RHDH audit log UI (if available) +**AND** the event is exportable via RHDH audit log export API (if available) + +### Scenario 7: Audit events in disconnected clusters + +**GIVEN** a disconnected cluster where RHDH central is unreachable +**WHEN** an ingestion audit event is emitted +**THEN** the event is written to the local audit log file (fallback mechanism) +**AND** the event is queued for transmission when connectivity is restored (if sync-to-central is enabled) +**AND** the event is not lost due to network partition +**AND** the event retains the same schema and content as centrally-logged events diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/eval-hub-integration/spec.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/eval-hub-integration/spec.md new file mode 100644 index 00000000000..4a900275d87 --- /dev/null +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/eval-hub-integration/spec.md @@ -0,0 +1,84 @@ +# Spec: Eval Hub Integration for Skill Quality Scores + +> **Status:** ✅ READY +> **Story:** RHIDP-15345 +> **Coverage:** Eval Hub endpoint configuration, quality score ingestion, per-skill score storage, aggregate distribution computation, graceful handling when Eval Hub unavailable, multiple eval backend support, score refresh cycle + +## Scenarios + +### Scenario 1: Eval Hub endpoint configuration via app-config + +**GIVEN** Boost is deployed with app-config containing `boost.evalHub.endpoint: 'https://eval-hub.example.com/api'` +**WHEN** the Boost backend starts +**THEN** the Eval Hub client is initialized with the configured endpoint +**AND** the client uses the endpoint for all Eval Hub API calls +**AND** if `boost.evalHub.enabled: false`, the Eval Hub client is not initialized and no score ingestion occurs +**AND** if the endpoint is missing or invalid, the backend logs a warning and disables Eval Hub integration + +### Scenario 2: Quality score ingestion from eval pipeline + +**GIVEN** the Eval Hub endpoint is configured and reachable +**WHEN** the Eval Hub ingestion service runs (on scheduled interval or manual trigger) +**THEN** the service calls `EvalHubClient.fetchQualityScores()` +**AND** the Eval Hub API returns an array of quality score results: `[{ skill_entity_ref, eval_source, score, timestamp, metadata }]` +**AND** the ingestion service stores each score in the `boost_quality_scores` DB table +**AND** scores are normalized to 0.0-1.0 range before storage (if eval source uses different scale) + +### Scenario 3: Per-skill score storage + +**GIVEN** the Eval Hub ingestion service fetches quality scores +**WHEN** a score is received for skill `skill:default/code-review` +**THEN** the score is stored in the DB with columns: `skill_entity_ref: 'skill:default/code-review'`, `eval_source: 'lighteval'`, `score: 0.85`, `timestamp: '2026-07-08T12:00:00Z'` +**AND** if a score for the same skill and eval source already exists, the new score is inserted (historical scores retained) +**AND** the DB table supports multiple scores per skill (e.g., daily quality scores over time) + +### Scenario 4: Aggregate distribution computation + +**GIVEN** multiple skills have quality scores stored in the DB +**WHEN** the Analytics API `GET /api/boost/admin/analytics/quality-scores` is called +**THEN** the aggregate distribution is computed on-demand by grouping scores into buckets: `0.0-0.2`, `0.2-0.4`, `0.4-0.6`, `0.6-0.8`, `0.8-1.0` +**AND** each bucket contains the count of skills with scores in that range +**AND** the distribution is returned as `{ score_ranges: [{ range: '0.0-0.2', count: 3 }, ...] }` +**AND** the computation uses only the latest score per skill (by timestamp) + +### Scenario 5: Graceful handling when Eval Hub unavailable + +**GIVEN** the Eval Hub endpoint is configured but unreachable (network error, service down) +**WHEN** the Eval Hub ingestion service runs +**THEN** the service logs a warning: `'Eval Hub unreachable, skipping quality score ingestion'` +**AND** the service does not crash or throw an unhandled error +**AND** the next scheduled ingestion attempt will retry +**AND** existing quality scores in the DB remain accessible via the Analytics API + +**GIVEN** `boost.evalHub.enabled: false` +**WHEN** the Eval Hub ingestion service is triggered +**THEN** the service logs: `'Eval Hub integration disabled, skipping ingestion'` +**AND** no API calls are made to the Eval Hub endpoint + +### Scenario 6: Multiple eval backend support (LightEval, IBM Clear, GuideLLM) + +**GIVEN** the Eval Hub API returns scores from different eval backends +**WHEN** the ingestion service fetches scores +**THEN** each score includes `eval_source: 'lighteval' | 'ibm-clear' | 'guidellm'` +**AND** scores from different eval sources are stored independently in the DB +**AND** the Analytics API can filter scores by `eval_source` query param +**AND** aggregate distribution can be computed per eval source or across all sources + +**GIVEN** a skill has scores from multiple eval backends +**WHEN** the Analytics API is called without `eval_source` filter +**THEN** the response includes scores from all eval sources +**AND** the aggregate distribution includes scores from all sources (unless filtered) + +### Scenario 7: Score refresh cycle + +**GIVEN** the Eval Hub ingestion service is configured with `boost.evalHub.refreshInterval: '1h'` +**WHEN** the Boost backend starts +**THEN** the ingestion service schedules a background task to run every 1 hour +**AND** each run fetches new quality scores from Eval Hub and stores them in the DB +**AND** the background task runs independently of user requests (asynchronous ingestion) +**AND** if the ingestion service crashes, the next scheduled run will retry + +**GIVEN** an admin wants to manually trigger quality score ingestion +**WHEN** the admin calls a manual ingestion endpoint (e.g., `POST /api/boost/admin/analytics/refresh-quality-scores`) +**THEN** the ingestion service immediately fetches and stores new scores +**AND** the manual trigger does not affect the scheduled refresh cycle diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/tasks.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/tasks.md new file mode 100644 index 00000000000..585966b0771 --- /dev/null +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/tasks.md @@ -0,0 +1,174 @@ +# Tasks: Ingestion Audit Logging and Metrics + +## 1. Audit Event Infrastructure (P0) — RHIDP-15343 + +- [ ] 1.1 Define ingestion audit event types in `plugins/boost-backend/src/audit/event-types.ts` +- [ ] 1.2 Define audit event payload schema extending RBAC audit event base schema +- [ ] 1.3 Create `IngestionAuditEvent` TypeScript interface with all event types +- [ ] 1.4 Create audit event emission helpers in `plugins/boost-backend/src/audit/ingestion-events.ts` +- [ ] 1.5 Integrate with RHDH audit log channel (same as RBAC events from RHIDP-15277) +- [ ] 1.6 Add audit event serialization to structured JSON +- [ ] 1.7 Add audit event validation (required fields, type constraints) +- [ ] 1.8 Add unit tests for audit event schema and emission helpers + +## 2. Sync Audit Integration (P0) — RHIDP-15343 + +- [ ] 2.1 Add `onSyncStart` lifecycle hook to connector base class +- [ ] 2.2 Emit `ingestion.sync.start` event in `onSyncStart` hook +- [ ] 2.3 Add `onSyncSuccess` lifecycle hook with asset counts parameter +- [ ] 2.4 Emit `ingestion.sync.success` event in `onSyncSuccess` hook with asset counts +- [ ] 2.5 Add `onSyncFailure` lifecycle hook with error parameter +- [ ] 2.6 Emit `ingestion.sync.failure` event in `onSyncFailure` hook with error details +- [ ] 2.7 Integrate audit event emission into existing connectors (GitHub, Jira, Confluence) +- [ ] 2.8 Add asset count tracking logic (added/updated/removed entities) +- [ ] 2.9 Add error serialization for sync failure events +- [ ] 2.10 Add integration tests for sync audit events end-to-end + +## 3. Config Change Audit (P0) — RHIDP-15343 + +- [ ] 3.1 Add config change tracking to connector config service +- [ ] 3.2 Emit `ingestion.config.change` event on config update +- [ ] 3.3 Capture actor identifier (user or service account) from request context +- [ ] 3.4 Capture before/after values for config changes +- [ ] 3.5 Add config field serialization (convert objects to strings for audit log) +- [ ] 3.6 Add integration tests for config change audit events + +## 4. Disconnected Cluster Support (P1) — RHIDP-15343 + +- [ ] 4.1 Add local audit log fallback when RHDH central unreachable +- [ ] 4.2 Add event queuing for sync-to-central when connectivity restored +- [ ] 4.3 Add event persistence to local file system +- [ ] 4.4 Add event replay mechanism for queued events +- [ ] 4.5 Add unit tests for disconnected cluster audit logging + +## 5. Analytics REST API (P1) — RHIDP-15344 + +- [ ] 5.1 Define analytics API routes in `plugins/boost-backend/src/api/admin/analytics/routes.ts` +- [ ] 5.2 Add `GET /api/boost/admin/analytics/sync-history` endpoint +- [ ] 5.3 Add `GET /api/boost/admin/analytics/quality-scores` endpoint +- [ ] 5.4 Add `GET /api/boost/admin/analytics/match-coverage` endpoint +- [ ] 5.5 Add RBAC gating to all endpoints (require `ai-catalog.admin` permission) +- [ ] 5.6 Add pagination support (page, limit, total_count metadata) +- [ ] 5.7 Add date range filtering (start_date, end_date query params) +- [ ] 5.8 Add connector name filtering for sync history endpoint +- [ ] 5.9 Add eval source filtering for quality scores endpoint +- [ ] 5.10 Add Express route registration in `plugins/boost-backend/src/plugin.ts` +- [ ] 5.11 Add integration tests for analytics API endpoints + +## 6. Sync History Service (P1) — RHIDP-15344 + +- [ ] 6.1 Create `AnalyticsService` in `plugins/boost-backend/src/analytics/service.ts` +- [ ] 6.2 Implement `getSyncHistory()` method querying audit log DB +- [ ] 6.3 Add sync history query with pagination +- [ ] 6.4 Add sync history query with date range filtering +- [ ] 6.5 Add sync history query with connector name filtering +- [ ] 6.6 Add sync duration calculation from start/end timestamps +- [ ] 6.7 Add unit tests for sync history service + +## 7. Quality Score Storage (P1) — RHIDP-15344, RHIDP-15345 + +- [ ] 7.1 Define `boost_quality_scores` DB table schema +- [ ] 7.2 Create DB migration for quality scores table in `plugins/boost-backend/src/db/migrations/` +- [ ] 7.3 Create `QualityScoresStore` in `plugins/boost-backend/src/db/quality-scores-store.ts` +- [ ] 7.4 Implement `insertScore()` method for storing quality scores +- [ ] 7.5 Implement `getScoresBySkill()` method for fetching per-skill scores +- [ ] 7.6 Implement `getLatestScores()` method for fetching latest scores per skill +- [ ] 7.7 Add DB indexes for performance (skill_entity_ref, timestamp) +- [ ] 7.8 Add unit tests for quality scores store + +## 8. Quality Score Analytics (P1) — RHIDP-15344, RHIDP-15345 + +- [ ] 8.1 Implement `getQualityScores()` method in `AnalyticsService` +- [ ] 8.2 Add per-skill quality scores query +- [ ] 8.3 Add aggregate distribution computation (group scores into buckets) +- [ ] 8.4 Add score normalization to 0.0-1.0 range +- [ ] 8.5 Add eval source filtering +- [ ] 8.6 Add unit tests for quality score analytics + +## 9. Match Coverage Calculator (P1) — RHIDP-15344 + +- [ ] 9.1 Create `MatchCoverageCalculator` in `plugins/boost-backend/src/analytics/match-coverage-calculator.ts` +- [ ] 9.2 Implement agent capability extraction from catalog entities +- [ ] 9.3 Implement skill ID extraction from catalog entities +- [ ] 9.4 Implement capability matching logic (agent capabilities vs. skill IDs) +- [ ] 9.5 Implement coverage ratio calculation (matched / total) +- [ ] 9.6 Implement unmatched capabilities list generation +- [ ] 9.7 Add caching for match coverage results (5 minute TTL) +- [ ] 9.8 Add unit tests for match coverage calculator + +## 10. Neo4j Sync Status (P1) — RHIDP-15344 + +- [ ] 10.1 Add Neo4j connection status check to `AnalyticsService` +- [ ] 10.2 Add Neo4j last sync timestamp query +- [ ] 10.3 Add Neo4j entity count query +- [ ] 10.4 Embed Neo4j status in all analytics API responses +- [ ] 10.5 Add graceful handling when Neo4j unavailable +- [ ] 10.6 Add unit tests for Neo4j status integration + +## 11. Eval Hub Client (P2) — RHIDP-15345 + +- [ ] 11.1 Define `EvalHubClient` interface in `plugins/boost-backend/src/eval-hub/client.ts` +- [ ] 11.2 Implement `fetchQualityScores()` method calling Eval Hub API +- [ ] 11.3 Add app-config schema for `boost.evalHub.endpoint`, `boost.evalHub.enabled` +- [ ] 11.4 Add Eval Hub client initialization from app-config +- [ ] 11.5 Add HTTP client with timeout and retry logic +- [ ] 11.6 Add error handling for Eval Hub API failures +- [ ] 11.7 Add score normalization to 0.0-1.0 range (handle different eval source scales) +- [ ] 11.8 Add unit tests for Eval Hub client + +## 12. Eval Hub Ingestion Service (P2) — RHIDP-15345 + +- [ ] 12.1 Create `EvalHubIngestionService` in `plugins/boost-backend/src/eval-hub/ingestion-service.ts` +- [ ] 12.2 Add scheduled background task for quality score ingestion +- [ ] 12.3 Add `boost.evalHub.refreshInterval` app-config option +- [ ] 12.4 Implement score fetching and storage logic +- [ ] 12.5 Add graceful handling when Eval Hub disabled or unavailable +- [ ] 12.6 Add manual ingestion trigger endpoint `POST /api/boost/admin/analytics/refresh-quality-scores` +- [ ] 12.7 Add ingestion status logging (success/failure, score count) +- [ ] 12.8 Add integration tests for Eval Hub ingestion + +## 13. Multi-Eval Backend Support (P2) — RHIDP-15345 + +- [ ] 13.1 Add `eval_source` field to quality scores DB table +- [ ] 13.2 Add eval source normalization in `EvalHubClient` (map backend-specific IDs to standard names) +- [ ] 13.3 Add eval source filtering in analytics API +- [ ] 13.4 Add per-eval-source aggregate distribution computation +- [ ] 13.5 Add support for LightEval, IBM Clear, GuideLLM eval backends +- [ ] 13.6 Add unit tests for multi-eval backend support + +## 14. Permission Definitions (P1) + +- [ ] 14.1 Define `ai-catalog.admin` permission in `plugins/boost-common/src/permissions.ts` (if not already defined) +- [ ] 14.2 Add permission rules for analytics endpoints +- [ ] 14.3 Add RBAC policy configuration examples in documentation +- [ ] 14.4 Add unit tests for permission checks + +## 15. Integration Testing (P1) + +- [ ] 15.1 Add end-to-end test for sync audit event flow (start → success → audit log) +- [ ] 15.2 Add end-to-end test for sync failure audit event flow +- [ ] 15.3 Add end-to-end test for config change audit event flow +- [ ] 15.4 Add end-to-end test for sync history API endpoint +- [ ] 15.5 Add end-to-end test for quality scores API endpoint +- [ ] 15.6 Add end-to-end test for match coverage API endpoint +- [ ] 15.7 Add end-to-end test for Eval Hub ingestion flow +- [ ] 15.8 Add end-to-end test for RBAC gating on analytics endpoints +- [ ] 15.9 Add end-to-end test for pagination and filtering + +## 16. Documentation (P2) + +- [ ] 16.1 Document audit event schema in `docs/audit-events.md` +- [ ] 16.2 Document analytics API endpoints in `docs/analytics-api.md` +- [ ] 16.3 Document Eval Hub integration setup in `docs/eval-hub-integration.md` +- [ ] 16.4 Add app-config examples for Eval Hub configuration +- [ ] 16.5 Add examples of analytics API usage (curl, fetch) +- [ ] 16.6 Add troubleshooting guide for Eval Hub connection issues +- [ ] 16.7 Add admin guide for interpreting analytics metrics + +## 17. Cross-Reference Updates (P2) + +- [ ] 17.1 Update RHDHPLAN-1508 (RBAC audit logging) cross-reference in shared audit infrastructure +- [ ] 17.2 Update RHDHPLAN-1509 (Analytics tab) cross-reference for API consumer +- [ ] 17.3 Update connector changes (RHDHPLAN-1510/1511/1512) for sync audit event emission +- [ ] 17.4 Update `ingestion-health-dashboard` change for audit event correlation +- [ ] 17.5 Update `connector-config-hot-reload` change for config change audit events diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/.openspec.yaml b/workspaces/boost/openspec/changes/ingestion-health-dashboard/.openspec.yaml new file mode 100644 index 00000000000..9df4af2b637 --- /dev/null +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/.openspec.yaml @@ -0,0 +1,3 @@ +schema: spec-driven +created: 2026-07-08 +status: draft diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md new file mode 100644 index 00000000000..d37c1257eac --- /dev/null +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md @@ -0,0 +1,121 @@ +# Design: Ingestion Health Admin Dashboard + +## Context + +Boost implements the ingestion health dashboard as a standard admin panel section following the existing patterns established in the augment workspace's admin panel (model connection, system prompt, agent config sections). The augment reference prototype has no equivalent feature — this is net-new functionality for Boost. The design follows RHDH Extensions Catalog patterns for admin tooling: backend exposes REST API, frontend renders via PatternFly components, RBAC enforced at API layer. + +## Goals + +- Per-connector health visibility without log diving +- Actionable error classification with diagnostic guidance +- Distinguish intentionally disabled connectors from unexpectedly failing ones in air-gapped clusters +- Force Sync capability for manual intervention +- Neo4j graph sync operational monitoring as distinct section +- Integrate seamlessly into existing augment admin panel navigation + +## Non-Goals + +- Real-time streaming ingestion (covered in RHDHPLAN-1514 Real-Time Ingestion future work) +- Implementing the connectors themselves (RHDHPLAN-1510/1511/1512) +- Defining RBAC policy model (RHDHPLAN-1508 RHIDP-15277) +- Auto-remediation of connector failures (admins manually force sync or fix root cause) +- Alerting/notification system for connector failures (future work) + +## Decisions + +### Decision 1: Health data model — sync attempts stored in database + +The health data model tracks sync attempts in a dedicated database table with schema: + +```sql +CREATE TABLE sync_attempts ( + id SERIAL PRIMARY KEY, + connector_id TEXT NOT NULL, + timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), + outcome TEXT NOT NULL CHECK (outcome IN ('success', 'failure')), + error_type TEXT, -- null for success + error_message TEXT, -- null for success + assets_added INTEGER NOT NULL DEFAULT 0, + assets_updated INTEGER NOT NULL DEFAULT 0, + assets_removed INTEGER NOT NULL DEFAULT 0, + duration_ms INTEGER NOT NULL, + INDEX (connector_id, timestamp DESC) +); +``` + +Health status is derived from the last N sync attempts (default N=3): + +- **Healthy**: Last 3 sync attempts succeeded +- **Degraded**: Last sync attempt succeeded but 1+ failures in last 3 attempts +- **Failing**: Last 3 sync attempts failed + +**Why:** Storing sync attempts provides historical context for health trends. Derivation logic (last N attempts) balances recency with resilience against transient failures. Separate table avoids coupling health storage to connector config schema. + +**How to apply:** Each connector provider's `run()` method reports sync outcomes to `HealthTracker.recordSyncAttempt()`. API queries the table for per-connector latest attempts and derives status. Retention policy: keep last 100 sync attempts per connector (configurable via `boost.ingestion.healthRetention.maxAttemptsPerConnector`). + +### Decision 2: Force Sync implementation — backend API endpoint triggers provider run() + +The Force Sync feature is implemented as `POST /api/boost/ingestion-health/:connectorId/force-sync` that triggers the connector provider's `run()` method outside the scheduled cadence. + +Implementation leverages `SchedulerService.triggerTask(taskId)` if available (for connectors registered as scheduled tasks). Falls back to direct `provider.run()` invocation if `SchedulerService` isn't integrated. + +Timeout is configurable (default 10 minutes) via `boost.ingestion.forceSyncTimeout`. Concurrent Force Sync prevention: endpoint returns 409 Conflict if connector is already running. + +**Why:** Force Sync is a manual override for cases where scheduled sync failed or admin needs immediate refresh (e.g., after fixing auth credentials). Timeout prevents hung requests. Concurrency prevention avoids duplicate work and state corruption. + +**How to apply:** API handler checks connector run state (via in-memory run registry or `SchedulerService.getTaskStatus()`), returns 409 if running. Otherwise calls `SchedulerService.triggerTask()` or `provider.run()` with timeout wrapper. Response includes `runId` for polling status. UI polls `GET /api/boost/ingestion-health/:connectorId/force-sync/:runId` for completion. + +### Decision 3: Admin UI in existing boost admin panel — new route/tab + +The ingestion health UI is a new section in the augment workspace's existing admin panel at route `/admin/ingestion-health`. Follows the pattern established by `/admin/model-connection`, `/admin/system-prompt`, `/admin/agent-config`. + +Navigation: Adds "Ingestion Health" item to admin panel sidebar (`AdminLayout.tsx`). Content: `IngestionHealthPanel.tsx` renders per-connector health cards using PatternFly `Card`, `CardHeader`, `CardBody` with status badges (`Label` component with green/yellow/red variants). + +Each card shows: + +- Connector name and type (GitHub, GitLab, Jira) +- Status badge (Healthy/Degraded/Failing/Disabled) +- Last sync attempt timestamp (relative time via `react-time-ago`) +- Last successful sync timestamp (or "Never" if no success) +- Error summary (if failing/degraded) with classification badge +- "Force Sync" button (disabled if already running) + +**Why:** Reusing existing admin panel reduces navigation complexity and follows established RHDH admin tooling patterns. PatternFly ensures consistency with RHDH design system. + +**How to apply:** Add route to `AdminLayout.tsx` routes array. `IngestionHealthPanel.tsx` fetches data from `GET /api/boost/ingestion-health` (returns array of connector health objects). Uses `useSWR` for polling (30s interval). Cards rendered in responsive grid layout (PatternFly `Gallery` component). + +### Decision 4: Disconnected-cluster UX — three-state model + +The health UI distinguishes between three connector states: + +1. **Enabled + Healthy**: Green status badge, no error indicators, shows sync timestamps +2. **Enabled + Failing**: Red status badge, error summary, diagnostic guidance, "Force Sync" action +3. **Disabled**: Grey/muted status badge, no error indicators, shows "Disabled" label, no "Force Sync" action + +Disabled state is set via connector config (`enabled: false`). Health status derivation skips disabled connectors entirely — they don't generate health records or consume health API response space. + +**Why:** Air-gapped deployments intentionally disable upstream connectors (GitHub, GitLab) but enable internal ones (Jira). Showing disabled connectors as "failing" generates false alarms. Muted treatment signals "expected state, no action needed." + +**How to apply:** Backend API filters disabled connectors before health status derivation. Frontend renders disabled connectors with `variant="outline"` PatternFly Label (grey), no error message, no "Force Sync" button. Admin can toggle `enabled` flag via connector config UI (separate feature, not in this change). + +### Decision 5: Neo4j panel as separate section — distinct from per-connector catalog sync + +The Neo4j graph sync status panel is a separate section within the Ingestion Health page, below per-connector cards. Renders as single card with distinct visual treatment (different header icon, "Knowledge Graph Sync" title). + +Shows: + +- Last sync timestamp (relative time) +- Sync outcome (success/failure) +- Node count (total nodes in graph) +- Relationship count (total relationships in graph) +- "Force Neo4j Re-sync" button with full/incremental toggle + +**Why:** Neo4j is a derived index, not a catalog entity source. It syncs from the catalog database, not external APIs. Treating it as a separate section prevents confusion with upstream connectors. Node/relationship counts are unique data points specific to graph storage. + +**How to apply:** `IngestionHealthPanel.tsx` renders Neo4j panel below connector cards, separated by PatternFly `Divider`. Fetches Neo4j sync status from `GET /api/boost/ingestion-health/neo4j`. "Force Neo4j Re-sync" calls `POST /api/boost/ingestion-health/neo4j/force-sync` with `mode` parameter (`full` or `incremental`). + +## Risks + +- **Health derivation lag**: Health status is derived from last N sync attempts, which may be stale if sync cadence is slow (e.g., hourly). Mitigated by showing last sync timestamp and supporting Force Sync for immediate refresh. +- **Sync attempt table growth**: Unbounded growth if retention policy isn't enforced. Mitigated by retention policy (last 100 attempts per connector) and scheduled cleanup job. +- **Force Sync timeout edge cases**: If connector run exceeds timeout, the sync may still complete in background but API returns timeout error. Mitigated by documenting timeout behavior and surfacing partial success state (e.g., "Sync in progress, check back later"). diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/proposal.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/proposal.md new file mode 100644 index 00000000000..1d58681fa66 --- /dev/null +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/proposal.md @@ -0,0 +1,59 @@ +# Proposal: Ingestion Health Admin Dashboard + +## Why + +Admins need visibility into connector health without reading raw logs. When catalog connectors fail, administrators face: + +- **Silent failures**: Connectors fail but no alerts fire — catalog goes stale, users see outdated data +- **Diagnostic friction**: Error messages in backend logs require SSH access, grep expertise, and correlation across connector instances +- **Air-gapped blind spots**: In disconnected clusters, connectivity failures are common and expected — but distinguishing "intentionally disabled" from "unexpectedly failing" requires manual inspection + +Air-gapped deployments make connectivity failures common and hard to diagnose. Administrators need a health dashboard that distinguishes intentionally disabled connectors from unexpectedly failing ones, surfaces actionable error classifications, and provides manual force-sync capability when automated scheduling isn't sufficient. + +## What Boost Builds + +### Health Status API + +- REST API exposing per-connector health: enabled state, last sync attempt/success timestamps, health status (healthy/degraded/failing), most recent error +- Data model tracks sync attempts with timestamps and outcomes (success/failure, assets added/updated/removed counts, error type, error message) +- Health status derivation: healthy = last 3 sync attempts succeeded, degraded = last sync succeeded but recent failures, failing = last 3 sync attempts failed +- RBAC-gated via boost admin permissions +- Force Sync API endpoint triggers provider's `run()` method outside scheduled cadence with configurable timeout + +### Admin Health UI + +- Admin dashboard section showing per-connector health cards with status indicators (PatternFly green/yellow/red), timestamps, error summaries, "Force Sync" buttons +- Built on PatternFly design system following existing admin panel patterns (model connection, system prompt, agent config sections) +- Loading states, error states, empty states for zero connectors +- Integrates into augment workspace's existing admin panel navigation + +### Error Classification + +- Connector failures classified into: auth/authorization failure, network/DNS/connectivity, schema/parsing mismatch, rate limiting +- Each classification includes diagnostic guidance (e.g., "Check service account credentials" for auth failures, "Verify DNS resolution" for network failures) +- Classification logic shared across all connectors via common error detection utilities +- Unknown errors fall back to generic classification with raw error message + +### Neo4j Graph Sync Status Panel + +- Operational monitoring for RHIDP-15295's Neo4j Knowledge Graph Sync Adapter: last sync timestamp, success/failure, node/relationship counts, "Force Neo4j Re-sync" action (full or incremental) +- Distinct from per-connector catalog sync panels because Neo4j is a derived index, not a catalog entity source +- Shows node/relationship counts as unique data points specific to graph storage + +### Disconnected-Cluster Health View Differentiation + +- Three-state model: enabled+healthy, enabled+failing, disabled +- Disabled connectors shown with grey/muted treatment (not error state) +- Enabled+failing connectors shown with red/alert indicators +- No "disabled" state generates alerts or error indicators — prevents false alarms in air-gapped environments + +## Impact + +- `plugins/boost-backend/src/api/ingestion-health.ts` — health status API routes +- `plugins/boost-backend/src/database/sync-attempts.ts` — sync attempts table schema and queries +- `plugins/boost-common/src/types/ingestion-health.ts` — health status types +- `plugins/boost-backend/src/ingestion/health-tracker.ts` — health status derivation logic +- `plugins/boost-backend/src/ingestion/error-classifier.ts` — error classification utilities +- `workspaces/augment/plugins/augment/src/components/AdminPanels/IngestionHealthPanel.tsx` — admin UI component +- `workspaces/augment/plugins/augment/src/components/AdminPanels/AdminLayout.tsx` — navigation integration +- Connector providers (`github-connector`, `gitlab-connector`, `jira-connector`) — integrate health tracking hooks diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/admin-health-ui/spec.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/admin-health-ui/spec.md new file mode 100644 index 00000000000..ed37f91696b --- /dev/null +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/admin-health-ui/spec.md @@ -0,0 +1,166 @@ +# Admin Health UI + +> **Status: Draft** — Pre-implementation specification. Subject to change during implementation. + +Admin dashboard section showing per-connector health cards with status indicators, timestamps, error summaries, Force Sync buttons. Neo4j panel with node/relationship counts, Force Neo4j Re-sync. Disconnected-cluster differentiation (disabled vs failing). Built on PatternFly design system. + +## ADDED Requirements + +### Requirement: Per-Connector Health Cards with PatternFly Design + +Health cards display connector status in admin panel. + +#### Scenario: Health card renders connector status + +- **WHEN** the Ingestion Health admin panel loads +- **THEN** each configured connector renders as a PatternFly `Card` with `CardHeader` and `CardBody` +- **AND** the card header shows connector name, type, and status badge (PatternFly `Label` with variant `success`/`warning`/`danger`/`outline`) +- **AND** status badge colors: green for Healthy, yellow for Degraded, red for Failing, grey for Disabled + +#### Scenario: Health card shows timestamps + +- **WHEN** a health card is rendered for a connector with sync history +- **THEN** the card body shows "Last sync attempt: [relative time]" (e.g., "5 minutes ago") using `react-time-ago` +- **AND** the card body shows "Last successful sync: [relative time]" or "Never" if no successful syncs exist +- **AND** timestamps are updated automatically as time passes (react-time-ago handles this) + +#### Scenario: Health card shows sync metrics + +- **WHEN** a health card is rendered for a connector with recent successful sync +- **THEN** the card body shows sync metrics: "Assets added: X, updated: Y, removed: Z" +- **AND** metrics are from the most recent successful sync attempt + +#### Scenario: Health card shows error summary for failing/degraded connectors + +- **WHEN** a health card is rendered for a connector with Failing or Degraded status +- **THEN** the card body shows error summary section with error type badge (auth/network/schema/rate-limit/unknown) and diagnostic guidance text +- **AND** error type badge uses PatternFly `Label` with color coding (red for auth/schema, orange for network/rate-limit, grey for unknown) + +#### Scenario: Force Sync button in health card + +- **WHEN** a health card is rendered for an enabled connector +- **THEN** the card footer shows a "Force Sync" button (PatternFly `Button` with variant `secondary`) +- **AND** clicking the button triggers `POST /api/boost/ingestion-health/:connectorId/force-sync` +- **AND** the button is disabled while the connector is running (shows spinner icon and "Syncing..." label) + +#### Scenario: Health card disabled state + +- **WHEN** a health card is rendered for a disabled connector +- **THEN** the status badge is grey with "Disabled" label +- **AND** no error summary is shown (even if last sync before disabling failed) +- **AND** the "Force Sync" button is hidden + +### Requirement: Neo4j Sync Panel with Node/Relationship Counts + +Neo4j graph sync status shown as separate section. + +#### Scenario: Neo4j panel renders below connector cards + +- **WHEN** the Ingestion Health admin panel loads +- **THEN** the Neo4j panel renders below per-connector cards, separated by PatternFly `Divider` +- **AND** the panel header shows "Knowledge Graph Sync" title with graph icon + +#### Scenario: Neo4j panel shows sync status + +- **WHEN** the Neo4j panel is rendered +- **THEN** the panel shows last sync timestamp (relative time), sync outcome badge (success/failure), node count, relationship count +- **AND** outcome badge uses PatternFly `Label` with variant `success` (green) or `danger` (red) + +#### Scenario: Force Neo4j Re-sync action + +- **WHEN** the Neo4j panel is rendered +- **THEN** the panel shows "Force Re-sync" button with mode toggle (PatternFly `ToggleGroup` with "Full" and "Incremental" options) +- **AND** clicking the button triggers `POST /api/boost/ingestion-health/neo4j/force-sync` with selected mode +- **AND** the button is disabled while Neo4j sync is running (shows spinner icon and "Syncing..." label) + +#### Scenario: Neo4j panel error state + +- **WHEN** the Neo4j panel is rendered and the last sync failed +- **THEN** the panel shows error summary section with error message and diagnostic guidance +- **AND** node/relationship counts show the last successful sync's counts (not current failed state) + +### Requirement: Disconnected-Cluster Differentiation + +Disabled connectors visually distinct from failing ones. + +#### Scenario: Disabled connector uses muted treatment + +- **WHEN** a connector is disabled (config `enabled: false`) +- **THEN** the health card renders with grey/muted status badge (`variant="outline"` PatternFly Label) +- **AND** no red/error indicators are shown +- **AND** the card body shows "Connector is disabled. Enable in connector config to resume sync." message + +#### Scenario: Failing connector uses alert treatment + +- **WHEN** a connector is enabled but failing (last 3 sync attempts failed) +- **THEN** the health card renders with red status badge (`variant="danger"` PatternFly Label) +- **AND** error summary section is shown with diagnostic guidance +- **AND** "Force Sync" button is enabled for manual retry + +#### Scenario: Healthy connector uses success treatment + +- **WHEN** a connector is enabled and healthy (last 3 sync attempts succeeded) +- **THEN** the health card renders with green status badge (`variant="success"` PatternFly Label) +- **AND** no error indicators are shown +- **AND** sync metrics are displayed in card body + +### Requirement: Loading States and Error Handling + +UI handles loading and error states gracefully. + +#### Scenario: Loading state while fetching health data + +- **WHEN** the Ingestion Health admin panel mounts and API request is in flight +- **THEN** the panel shows PatternFly `Spinner` with "Loading connector health..." message +- **AND** no cards are rendered until data is available + +#### Scenario: Error state for API failure + +- **WHEN** the API request to `GET /api/boost/ingestion-health` fails (e.g., network error, 500 response) +- **THEN** the panel shows PatternFly `EmptyState` with error icon and message "Failed to load connector health. Refresh to retry." +- **AND** a "Refresh" button is shown that triggers API refetch + +#### Scenario: Empty state for zero connectors + +- **WHEN** the API returns an empty array (no connectors configured) +- **THEN** the panel shows PatternFly `EmptyState` with info icon and message "No connectors configured. Configure connectors in the admin panel." +- **AND** no cards are rendered + +#### Scenario: Force Sync error handling + +- **WHEN** Force Sync API call fails (e.g., 409 Conflict, 504 Timeout) +- **THEN** the health card shows PatternFly `Alert` with error message (e.g., "Connector is already running" or "Sync operation timed out") +- **AND** the "Force Sync" button re-enables after error alert is dismissed + +### Requirement: Navigation Integration + +Ingestion Health section integrated into admin panel navigation. + +#### Scenario: Admin panel sidebar includes Ingestion Health item + +- **WHEN** the admin panel loads (`/admin` route) +- **THEN** the sidebar navigation includes "Ingestion Health" item below existing sections (Model Connection, System Prompt, Agent Config) +- **AND** clicking the item navigates to `/admin/ingestion-health` route + +#### Scenario: Ingestion Health route renders IngestionHealthPanel + +- **WHEN** the user navigates to `/admin/ingestion-health` +- **THEN** the `IngestionHealthPanel` component is rendered in the main content area +- **AND** the sidebar "Ingestion Health" item is highlighted as active + +### Requirement: Polling for Real-Time Updates + +Health data refreshes automatically via polling. + +#### Scenario: Health data polls every 30 seconds + +- **WHEN** the Ingestion Health admin panel is mounted +- **THEN** the API `GET /api/boost/ingestion-health` is called initially and every 30 seconds thereafter +- **AND** polling uses `useSWR` with `refreshInterval: 30000` (30 seconds) +- **AND** polling stops when the panel unmounts (user navigates away) + +#### Scenario: Force Sync status polling + +- **WHEN** a Force Sync is triggered and the API returns a `runId` +- **THEN** the UI polls `GET /api/boost/ingestion-health/:connectorId/force-sync/:runId` every 2 seconds until status is `success` or `failure` +- **AND** the health card updates with latest sync metrics once Force Sync completes diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/error-classification/spec.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/error-classification/spec.md new file mode 100644 index 00000000000..d24626d8ffc --- /dev/null +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/error-classification/spec.md @@ -0,0 +1,154 @@ +# Error Classification + +> **Status: Draft** — Pre-implementation specification. Subject to change during implementation. + +Connector failures classified into actionable categories with diagnostic guidance. Classification logic shared across all connectors. + +## ADDED Requirements + +### Requirement: Auth/Authorization Failure Classification + +Authentication and authorization errors identified and classified. + +#### Scenario: Auth failure classified with diagnostic guidance + +- **WHEN** a connector sync fails with authentication error (e.g., "401 Unauthorized", "Invalid API token", "OAuth token expired") +- **THEN** the error is classified as `error_type: "auth"` +- **AND** `diagnosticGuidance` is `"Check service account credentials in connector config. Verify API token is valid and has required permissions."` +- **AND** error classification is performed via `ErrorClassifier.classify(error)` utility function + +#### Scenario: OAuth token expiry detected + +- **WHEN** a connector sync fails with OAuth-specific error (e.g., "Access token expired", "Refresh token invalid") +- **THEN** the error is classified as `error_type: "auth"` +- **AND** `diagnosticGuidance` is `"OAuth token expired. Re-authenticate the connector in admin panel or refresh the access token."` + +#### Scenario: Insufficient permissions detected + +- **WHEN** a connector sync fails with permission error (e.g., "403 Forbidden", "Insufficient scopes") +- **THEN** the error is classified as `error_type: "auth"` +- **AND** `diagnosticGuidance` is `"Service account lacks required permissions. Verify API token has read access to required resources (repositories, projects, issues)."` + +### Requirement: Network/DNS/Connectivity Failure Classification + +Network-related errors identified and classified. + +#### Scenario: Network failure classified with diagnostic guidance + +- **WHEN** a connector sync fails with network error (e.g., "ECONNREFUSED", "ETIMEDOUT", "Network unreachable") +- **THEN** the error is classified as `error_type: "network"` +- **AND** `diagnosticGuidance` is `"Network connectivity issue. Verify DNS resolution, firewall rules, and external service availability. In air-gapped clusters, disable connectors for unreachable external services."` + +#### Scenario: DNS resolution failure detected + +- **WHEN** a connector sync fails with DNS error (e.g., "ENOTFOUND", "DNS lookup failed") +- **THEN** the error is classified as `error_type: "network"` +- **AND** `diagnosticGuidance` is `"DNS resolution failed. Verify DNS configuration and external service hostname. In disconnected clusters, this is expected for external services—consider disabling the connector."` + +#### Scenario: TLS/SSL certificate error detected + +- **WHEN** a connector sync fails with TLS error (e.g., "UNABLE_TO_VERIFY_LEAF_SIGNATURE", "self signed certificate") +- **THEN** the error is classified as `error_type: "network"` +- **AND** `diagnosticGuidance` is `"TLS/SSL certificate verification failed. Verify certificate chain or configure connector to trust custom CA certificates."` + +### Requirement: Schema/Parsing Mismatch Classification + +Data schema and parsing errors identified and classified. + +#### Scenario: Schema mismatch classified with diagnostic guidance + +- **WHEN** a connector sync fails with schema error (e.g., "Unexpected field 'x' in response", "Missing required field 'y'") +- **THEN** the error is classified as `error_type: "schema"` +- **AND** `diagnosticGuidance` is `"API response schema mismatch. This may indicate an upstream API version change. Check connector logs for expected vs actual schema and consider updating the connector."` + +#### Scenario: JSON parsing error detected + +- **WHEN** a connector sync fails with parsing error (e.g., "Unexpected token < in JSON", "SyntaxError: JSON.parse") +- **THEN** the error is classified as `error_type: "schema"` +- **AND** `diagnosticGuidance` is `"Failed to parse API response. Upstream service may be returning HTML error page or malformed JSON. Check connector logs for raw response body."` + +#### Scenario: GraphQL query error detected + +- **WHEN** a connector sync fails with GraphQL error (e.g., "Field 'x' doesn't exist on type 'Y'", "Cannot query field") +- **THEN** the error is classified as `error_type: "schema"` +- **AND** `diagnosticGuidance` is `"GraphQL query schema mismatch. Upstream GraphQL schema may have changed. Review connector GraphQL queries against current API schema."` + +### Requirement: Rate Limiting Classification + +Rate limit errors identified and classified. + +#### Scenario: Rate limit classified with diagnostic guidance + +- **WHEN** a connector sync fails with rate limit error (e.g., "429 Too Many Requests", "Rate limit exceeded", "X-RateLimit-Remaining: 0") +- **THEN** the error is classified as `error_type: "rate-limit"` +- **AND** `diagnosticGuidance` is `"API rate limit exceeded. Connector will retry on next scheduled sync. Consider increasing sync interval or requesting higher rate limits from service provider."` + +#### Scenario: GitHub-specific rate limit detected + +- **WHEN** a GitHub connector sync fails with rate limit headers (e.g., `X-RateLimit-Remaining: 0`) +- **THEN** the error is classified as `error_type: "rate-limit"` +- **AND** `diagnosticGuidance` includes retry timestamp from `X-RateLimit-Reset` header (e.g., `"Rate limit will reset at 2:30 PM UTC. Connector will retry automatically."`) + +#### Scenario: Secondary rate limit detected + +- **WHEN** a connector sync fails with secondary rate limit (e.g., GitHub "You have exceeded a secondary rate limit") +- **THEN** the error is classified as `error_type: "rate-limit"` +- **AND** `diagnosticGuidance` is `"Secondary rate limit triggered (too many requests in short period). Connector will back off and retry. Consider reducing sync frequency."` + +### Requirement: Shared Classification Logic Across Connectors + +Classification logic is reusable across GitHub, GitLab, Jira connectors. + +#### Scenario: ErrorClassifier utility function + +- **WHEN** a connector provider calls `ErrorClassifier.classify(error)` +- **THEN** the function returns classification object with `errorType`, `errorMessage`, `diagnosticGuidance` fields +- **AND** classification logic is connector-agnostic (detects error patterns from HTTP status codes, error messages, error codes) + +#### Scenario: Connector-specific error detection + +- **WHEN** a connector has provider-specific error patterns (e.g., GitHub secondary rate limit, Jira Cloud-specific auth errors) +- **THEN** the `ErrorClassifier` supports optional connector-specific matchers via `ErrorClassifier.classify(error, { connectorType: 'github' })` +- **AND** base classification runs first, connector-specific matchers override if matched + +#### Scenario: Error classification logging + +- **WHEN** an error is classified +- **THEN** the classification result is logged at `debug` level with raw error for troubleshooting +- **AND** log includes: `connectorId`, `errorType`, `errorMessage`, `rawError` (stack trace), `diagnosticGuidance` + +### Requirement: Unknown Error Fallback Classification + +Errors that don't match known patterns fall back to generic classification. + +#### Scenario: Unknown error fallback + +- **WHEN** a connector sync fails and the error doesn't match any classification patterns +- **THEN** the error is classified as `error_type: "unknown"` +- **AND** `diagnosticGuidance` is `"Unknown error occurred. Check connector logs for detailed error trace and stack trace."` +- **AND** `errorMessage` contains the raw error string for admin inspection + +#### Scenario: Multi-error classification + +- **WHEN** a connector sync fails with multiple errors (e.g., network timeout after auth failure) +- **THEN** the error is classified based on the first classifiable error in the chain +- **AND** if no errors match known patterns, falls back to `error_type: "unknown"` + +### Requirement: Error Classification in Health Cards + +Classified errors rendered in admin UI with color-coded badges. + +#### Scenario: Error type badge color coding + +- **WHEN** a health card renders error summary for a failing/degraded connector +- **THEN** the error type badge color is: + - Red for `auth` and `schema` (requires immediate admin action) + - Orange for `network` and `rate-limit` (may resolve automatically) + - Grey for `unknown` (requires log inspection) +- **AND** badge uses PatternFly `Label` component with appropriate `color` prop + +#### Scenario: Diagnostic guidance displayed in health card + +- **WHEN** a health card renders error summary +- **THEN** the card body shows `diagnosticGuidance` text below error type badge +- **AND** guidance text is wrapped in PatternFly `Text` component with `variant="small"` for readability diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/health-status-api/spec.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/health-status-api/spec.md new file mode 100644 index 00000000000..35ee5a0e8c7 --- /dev/null +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/health-status-api/spec.md @@ -0,0 +1,135 @@ +# Health Status API + +> **Status: Draft** — Pre-implementation specification. Subject to change during implementation. + +REST API exposing per-connector health status and force-sync capabilities. Data model tracks sync attempts with timestamps and outcomes. API is RBAC-gated via boost admin permissions. + +## ADDED Requirements + +### Requirement: Health Status API Returns Per-Connector Health + +The API exposes connector health state for admin dashboard consumption. + +#### Scenario: API returns per-connector health objects + +- **WHEN** `GET /api/boost/ingestion-health` is called with valid boost admin credentials +- **THEN** the response is a JSON array of connector health objects +- **AND** each object contains: `connectorId`, `connectorType`, `enabled`, `status` (healthy/degraded/failing/disabled), `lastSyncAttempt` (ISO timestamp), `lastSuccessfulSync` (ISO timestamp or null), `errorSummary` (object or null), `metrics` (object with assetsAdded/Updated/Removed counts) +- **AND** disabled connectors are excluded from the response unless `?includeDisabled=true` query parameter is set + +#### Scenario: Health status derivation logic + +- **WHEN** a connector has 3+ sync attempts in the database +- **THEN** the API derives status from the last 3 attempts: + - **Healthy** if all 3 succeeded + - **Degraded** if the most recent succeeded but at least 1 of the prior 2 failed + - **Failing** if all 3 failed +- **AND** if fewer than 3 attempts exist, status is derived from available attempts (e.g., 1 success = Healthy) + +#### Scenario: Empty state for zero connectors + +- **WHEN** `GET /api/boost/ingestion-health` is called and no connectors are configured +- **THEN** the response is an empty JSON array `[]` +- **AND** the HTTP status is 200 OK + +### Requirement: Data Model Tracks Sync Attempts + +Each connector sync attempt is recorded in the database. + +#### Scenario: Sync attempt recorded in database + +- **WHEN** a connector completes a sync attempt (success or failure) +- **THEN** a record is inserted into the `sync_attempts` table with: `connector_id`, `timestamp`, `outcome` (success/failure), `error_type`, `error_message`, `assets_added`, `assets_updated`, `assets_removed`, `duration_ms` +- **AND** the record is queryable via health API within 1 second + +#### Scenario: Retention policy enforced + +- **WHEN** a connector has more than 100 sync attempts in the database +- **THEN** the oldest attempts beyond the retention limit are deleted via scheduled cleanup job +- **AND** the retention limit is configurable via `boost.ingestion.healthRetention.maxAttemptsPerConnector` (default 100) + +#### Scenario: Sync metrics calculation + +- **WHEN** a connector sync succeeds +- **THEN** `assets_added`, `assets_updated`, `assets_removed` counts are populated based on connector provider's diff logic +- **AND** if the provider doesn't report metrics, all three fields default to 0 + +### Requirement: RBAC Gating via Boost Admin Permissions + +Access to the health API is restricted to boost admins. + +#### Scenario: Unauthorized user receives 403 + +- **WHEN** `GET /api/boost/ingestion-health` is called without boost admin permissions +- **THEN** the response is HTTP 403 Forbidden with error message `"Insufficient permissions to view ingestion health"` +- **AND** the request is logged in the audit log (per RHDHPLAN-1508 RHIDP-15277 audit logging pattern) + +#### Scenario: Admin user receives health data + +- **WHEN** `GET /api/boost/ingestion-health` is called with valid boost admin credentials +- **THEN** the response is HTTP 200 OK with connector health array +- **AND** the request is logged in the audit log with user identity and timestamp + +### Requirement: Force Sync API Endpoint + +Admins can manually trigger connector sync outside scheduled cadence. + +#### Scenario: Force Sync triggers connector run + +- **WHEN** `POST /api/boost/ingestion-health/:connectorId/force-sync` is called with valid boost admin credentials +- **THEN** the connector provider's `run()` method is invoked immediately +- **AND** the response includes a `runId` for polling status +- **AND** the sync attempt is recorded in the `sync_attempts` table upon completion + +#### Scenario: Force Sync timeout handling + +- **WHEN** `POST /api/boost/ingestion-health/:connectorId/force-sync` is called and the sync exceeds the timeout limit +- **THEN** the response is HTTP 504 Gateway Timeout with error message `"Sync operation exceeded timeout of 10 minutes"` +- **AND** the timeout limit is configurable via `boost.ingestion.forceSyncTimeout` (default 10 minutes) +- **AND** the connector run continues in the background (not killed) + +#### Scenario: Concurrent Force Sync prevention + +- **WHEN** `POST /api/boost/ingestion-health/:connectorId/force-sync` is called while the connector is already running +- **THEN** the response is HTTP 409 Conflict with error message `"Connector is already running (runId: abc123)"` +- **AND** the response includes the existing `runId` for polling + +#### Scenario: Force Sync status polling + +- **WHEN** `GET /api/boost/ingestion-health/:connectorId/force-sync/:runId` is called +- **THEN** the response includes: `runId`, `status` (running/success/failure), `startTime`, `endTime` (null if running), `errorSummary` (null if success or running) +- **AND** if the run is complete, the response includes sync metrics (assetsAdded/Updated/Removed) + +### Requirement: Neo4j Graph Sync Status API + +Neo4j Knowledge Graph Sync Adapter health exposed via dedicated endpoint. + +#### Scenario: Neo4j sync status retrieved + +- **WHEN** `GET /api/boost/ingestion-health/neo4j` is called with valid boost admin credentials +- **THEN** the response includes: `lastSyncTimestamp`, `outcome` (success/failure), `nodeCount`, `relationshipCount`, `errorSummary` (null if success) +- **AND** counts represent the current state of the Neo4j graph (not delta) + +#### Scenario: Force Neo4j Re-sync + +- **WHEN** `POST /api/boost/ingestion-health/neo4j/force-sync` is called with `mode` parameter (`full` or `incremental`) +- **THEN** the Neo4j sync adapter is triggered with the specified mode +- **AND** the response includes a `runId` for polling status +- **AND** full mode clears and rebuilds the entire graph, incremental mode syncs only catalog changes since last sync + +### Requirement: Error Summary Structure + +Error summaries provide actionable diagnostic context. + +#### Scenario: Error summary includes classification + +- **WHEN** a connector sync fails and the error is classified +- **THEN** the `errorSummary` object includes: `errorType` (auth/network/schema/rate-limit/unknown), `errorMessage` (raw error), `diagnosticGuidance` (actionable next steps) +- **AND** `diagnosticGuidance` is a human-readable string (e.g., "Check service account credentials in connector config") + +#### Scenario: Unknown error fallback + +- **WHEN** a connector sync fails and the error cannot be classified +- **THEN** the `errorSummary.errorType` is `"unknown"` +- **AND** `errorSummary.diagnosticGuidance` is `"Check connector logs for detailed error trace"` +- **AND** `errorSummary.errorMessage` contains the raw error string diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/tasks.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/tasks.md new file mode 100644 index 00000000000..929cbb4fd91 --- /dev/null +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/tasks.md @@ -0,0 +1,121 @@ +# Tasks: Ingestion Health Admin Dashboard + +## 1. Health Data Model and Storage (P0) — RHIDP-15335 + +- [ ] 1.1 Define `sync_attempts` table schema in database migration (connector_id, timestamp, outcome, error_type, error_message, assets_added/updated/removed, duration_ms) +- [ ] 1.2 Create database migration file for `sync_attempts` table with indexes on (connector_id, timestamp DESC) +- [ ] 1.3 Implement `SyncAttemptsRepository` class with methods: `insertSyncAttempt()`, `getLatestAttempts(connectorId, limit)`, `cleanupOldAttempts(connectorId, retentionLimit)` +- [ ] 1.4 Add retention policy config schema to `boost.ingestion.healthRetention.maxAttemptsPerConnector` (default 100) +- [ ] 1.5 Implement scheduled cleanup job for sync attempts (runs daily, enforces retention policy per connector) +- [ ] 1.6 Add database indexes for efficient health status queries + +## 2. Health Status API (P0) — RHIDP-15335 + +- [ ] 2.1 Define `ConnectorHealthStatus` type in `plugins/boost-common/src/types/ingestion-health.ts` (connectorId, connectorType, enabled, status, lastSyncAttempt, lastSuccessfulSync, errorSummary, metrics) +- [ ] 2.2 Implement `GET /api/boost/ingestion-health` route returning array of connector health objects +- [ ] 2.3 Implement health status derivation logic in `HealthStatusService.deriveStatus(attempts)` (healthy/degraded/failing based on last 3 attempts) +- [ ] 2.4 Add `?includeDisabled=true` query parameter support for disabled connectors +- [ ] 2.5 Implement RBAC gating via boost admin permissions check in route handler +- [ ] 2.6 Add audit logging for health API requests (per RHDHPLAN-1508 RHIDP-15277 audit logging pattern) +- [ ] 2.7 Implement empty state handling (returns `[]` for zero connectors) +- [ ] 2.8 Add health API integration tests (authorized/unauthorized, enabled/disabled filters, health status derivation) + +## 3. Error Classification (P0) — RHIDP-15337 + +- [ ] 3.1 Create `ErrorClassifier` utility class in `plugins/boost-backend/src/ingestion/error-classifier.ts` +- [ ] 3.2 Implement `classify(error, options?)` method returning `{ errorType, errorMessage, diagnosticGuidance }` +- [ ] 3.3 Add auth failure detection patterns (401/403 status, "Invalid token", "OAuth expired", "Insufficient scopes") +- [ ] 3.4 Add network failure detection patterns (ECONNREFUSED, ETIMEDOUT, DNS errors, TLS errors) +- [ ] 3.5 Add schema mismatch detection patterns (JSON parsing errors, "Unexpected field", GraphQL query errors) +- [ ] 3.6 Add rate limit detection patterns (429 status, "Rate limit exceeded", X-RateLimit-Remaining headers) +- [ ] 3.7 Implement connector-specific error matchers (GitHub secondary rate limit, Jira Cloud auth errors) +- [ ] 3.8 Implement unknown error fallback classification +- [ ] 3.9 Add diagnostic guidance text for each error type +- [ ] 3.10 Add error classification unit tests (each error type pattern, fallback, connector-specific) + +## 4. Admin Health UI (P1) — RHIDP-15336, RHIDP-15339 + +- [ ] 4.1 Create `IngestionHealthPanel.tsx` component in `workspaces/augment/plugins/augment/src/components/AdminPanels/` +- [ ] 4.2 Implement health card rendering with PatternFly `Card`, `CardHeader`, `CardBody` components +- [ ] 4.3 Add status badge rendering (PatternFly `Label` with success/warning/danger/outline variants for healthy/degraded/failing/disabled) +- [ ] 4.4 Add timestamp rendering with `react-time-ago` (last sync attempt, last successful sync) +- [ ] 4.5 Add sync metrics display (assets added/updated/removed counts) +- [ ] 4.6 Add error summary section with error type badge and diagnostic guidance text +- [ ] 4.7 Add "Force Sync" button with disabled state during connector run (PatternFly `Button` with spinner) +- [ ] 4.8 Implement disabled connector visual treatment (grey badge, no error indicators, hidden Force Sync button) +- [ ] 4.9 Add loading state (PatternFly `Spinner` with "Loading connector health..." message) +- [ ] 4.10 Add error state (PatternFly `EmptyState` with "Failed to load" message and Refresh button) +- [ ] 4.11 Add empty state (PatternFly `EmptyState` with "No connectors configured" message) +- [ ] 4.12 Implement health data polling via `useSWR` with 30s refresh interval +- [ ] 4.13 Add responsive grid layout for health cards (PatternFly `Gallery` component) + +## 5. Neo4j Sync Panel (P1) — RHIDP-15338 + +- [ ] 5.1 Implement `GET /api/boost/ingestion-health/neo4j` route returning Neo4j sync status +- [ ] 5.2 Add Neo4j panel rendering in `IngestionHealthPanel.tsx` below connector cards (PatternFly `Divider` separator) +- [ ] 5.3 Add Neo4j panel header with "Knowledge Graph Sync" title and graph icon +- [ ] 5.4 Add sync status display (last sync timestamp, outcome badge, node count, relationship count) +- [ ] 5.5 Add "Force Re-sync" button with mode toggle (PatternFly `ToggleGroup` for Full/Incremental) +- [ ] 5.6 Implement `POST /api/boost/ingestion-health/neo4j/force-sync` route with mode parameter +- [ ] 5.7 Add Neo4j panel error state (error summary, show last successful counts) +- [ ] 5.8 Add Neo4j panel loading state (spinner during force sync) +- [ ] 5.9 Integrate Neo4j sync adapter health tracking hooks + +## 6. Force Sync (P1) — RHIDP-15335, RHIDP-15336 + +- [ ] 6.1 Implement `POST /api/boost/ingestion-health/:connectorId/force-sync` route +- [ ] 6.2 Add connector run state registry (in-memory or via SchedulerService.getTaskStatus()) +- [ ] 6.3 Implement concurrent Force Sync prevention (return 409 if already running) +- [ ] 6.4 Implement Force Sync trigger via SchedulerService.triggerTask() or direct provider.run() invocation +- [ ] 6.5 Add Force Sync timeout wrapper (configurable via `boost.ingestion.forceSyncTimeout`, default 10 min) +- [ ] 6.6 Implement `GET /api/boost/ingestion-health/:connectorId/force-sync/:runId` status polling route +- [ ] 6.7 Add Force Sync UI polling in health card (poll every 2s until status is success/failure) +- [ ] 6.8 Add Force Sync error handling in UI (409/504 error alerts) +- [ ] 6.9 Add Force Sync integration tests (trigger, timeout, concurrency prevention, polling) + +## 7. Connector Provider Integration (P1) + +- [ ] 7.1 Add `HealthTracker.recordSyncAttempt()` calls to GitHub connector provider's `run()` method +- [ ] 7.2 Add `HealthTracker.recordSyncAttempt()` calls to GitLab connector provider's `run()` method +- [ ] 7.3 Add `HealthTracker.recordSyncAttempt()` calls to Jira connector provider's `run()` method +- [ ] 7.4 Implement sync metrics calculation (assets added/updated/removed) in each provider +- [ ] 7.5 Add error capture and classification in each provider's error handlers +- [ ] 7.6 Verify health tracking doesn't introduce performance overhead (measure sync duration before/after) + +## 8. Navigation Integration (P1) + +- [ ] 8.1 Add "Ingestion Health" navigation item to `AdminLayout.tsx` sidebar +- [ ] 8.2 Add `/admin/ingestion-health` route to admin panel routes +- [ ] 8.3 Implement active state highlighting for Ingestion Health nav item +- [ ] 8.4 Verify navigation integration follows existing admin panel patterns (Model Connection, System Prompt, Agent Config) + +## 9. RBAC and Permissions (P1) + +- [ ] 9.1 Define boost admin permission check function (`requireBoostAdmin()` middleware) +- [ ] 9.2 Add permission check to all ingestion health API routes (GET health, POST force-sync, GET Neo4j, POST Neo4j force-sync) +- [ ] 9.3 Add 403 error handling in UI (show "Insufficient permissions" message) +- [ ] 9.4 Add permission check integration tests (verify 403 for non-admin users) + +## 10. Testing (P1) + +- [ ] 10.1 Add health status API unit tests (status derivation, retention policy, empty state, RBAC) +- [ ] 10.2 Add error classification unit tests (all error types, connector-specific patterns, fallback) +- [ ] 10.3 Add Force Sync integration tests (trigger, timeout, concurrency, polling) +- [ ] 10.4 Add Neo4j sync API tests (status retrieval, force re-sync, mode selection) +- [ ] 10.5 Add health UI component tests (card rendering, status badges, error summary, Force Sync button states) +- [ ] 10.6 Add end-to-end tests (full health dashboard flow: view health, force sync, poll status, see updated metrics) +- [ ] 10.7 Add cross-connector health tracking tests (verify health tracking works for GitHub, GitLab, Jira providers) + +## 11. Documentation (P2) + +- [ ] 11.1 Add admin user guide for Ingestion Health dashboard (how to interpret status, when to force sync, error diagnostic steps) +- [ ] 11.2 Add developer guide for connector health integration (how to add health tracking to new connectors) +- [ ] 11.3 Add configuration reference for health retention settings (`boost.ingestion.healthRetention`, `boost.ingestion.forceSyncTimeout`) +- [ ] 11.4 Add error classification reference table (error types, detection patterns, diagnostic guidance) + +## 12. Performance and Monitoring (P2) + +- [ ] 12.1 Add performance metrics for health API (response time, database query duration) +- [ ] 12.2 Add monitoring for sync attempt table growth (alert if retention cleanup fails) +- [ ] 12.3 Add monitoring for Force Sync timeouts (track timeout frequency per connector) +- [ ] 12.4 Optimize health status queries (ensure indexes are used, consider caching if needed) diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/.openspec.yaml b/workspaces/boost/openspec/changes/upstream-schema-alignment/.openspec.yaml new file mode 100644 index 00000000000..9df4af2b637 --- /dev/null +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/.openspec.yaml @@ -0,0 +1,3 @@ +schema: spec-driven +created: 2026-07-08 +status: draft diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/design.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/design.md new file mode 100644 index 00000000000..4dae3adf327 --- /dev/null +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/design.md @@ -0,0 +1,105 @@ +# Design: Upstream Schema Alignment Readiness + +## Context + +RHDH Boost has defined a set of AI Asset annotations and entity type conventions to represent agents, skills, MCP servers, AI models, and model servers in the Backstage catalog. These annotations (`rhdh.io/ai-asset-category`, `rhdh.io/ai-asset-version`) and `spec.type` values (`ai-agent`, `mcp-server`, etc.) are documented in the `agent-creation-discovery` change's `catalog-entities/spec.md`. + +Meanwhile, upstream Backstage has draft RFCs proposing new entity kinds: + +- **RFC #32062**: `McpServer` entity kind for MCP servers +- **RFC #33060**: `ai-model` and `ai-model-server` entity kinds + +These RFCs are in draft status — their schemas and field names may still change. RHDH must track these RFCs and prepare for eventual migration, but premature migration risks churn if RFC schemas change. + +The solution: a documented mapping from current RHDH annotations to draft RFC kinds (with confidence levels), plus a dry-run tool that reports what a migration would require — without executing it. + +## Goals + +- Document explicit mapping from current RHDH AI Asset annotations to draft upstream RFC entity kinds +- Provide confidence levels per mapping (high/medium/low) based on RFC stability +- Build read-only migration-readiness tooling that enumerates entities and reports transformation requirements +- Frame actual migration as explicit future work dependent on RFC finalization +- Make the specification accessible to platform engineers and customers + +## Non-Goals + +- Executing the actual migration (future work once RFCs finalize) +- Defining new upstream entity kinds or modifying RFCs +- Modifying existing catalog entities in production +- Implementing a catalog processor for migration (future work) +- Making claims about RFC finalization timelines + +## Decisions + +### Decision 1: Specification as living document + +The annotation specification maps current RHDH annotations to DRAFT RFC kinds. As RFCs evolve, the mapping updates. The document explicitly states "draft mapping, subject to RFC finalization." + +**Why:** RFCs #32062 and #33060 are in early draft. The mapping document must capture uncertainty and evolve alongside RFC changes. + +**How to apply:** Published alongside existing specs in `workspaces/boost/specifications/`. The document includes a header stating the draft status and last-updated date. Each mapping includes a confidence level and notes on RFC stability. + +### Decision 2: Mapping table structure with confidence levels + +For each RHDH entity type, the mapping table shows: + +- Current kind + `spec.type` +- Proposed RFC kind +- Fields requiring transformation +- Confidence level (high/medium/low) + +**Confidence levels:** + +- **High:** RFC schema is stable, fields are well-defined, unlikely to change +- **Medium:** RFC is active but still has open questions or alternative options under discussion +- **Low:** RFC hasn't addressed this entity type, or the mapping is speculative + +**Why:** RFCs evolve. A confidence level makes it clear which mappings are stable vs. which are subject to change. If RFCs adopt different schema options, the mapping document can capture "if RFC adopts option A, mapping is X; if option B, mapping is Y." + +**How to apply:** Each row in the mapping table includes a confidence column. The specification document includes a section explaining confidence levels and what they mean for migration planning. + +**Example mapping entries:** + +| AI Asset | Current Kind | spec.type | Target RFC Kind | Confidence | Transformation Requirements | +| ---------- | ------------ | ---------- | ---------------------- | ---------- | ----------------------------------------------------------------- | +| MCP Server | API | mcp-server | McpServer (RFC #32062) | Medium | Migrate kind from API → McpServer; preserve `spec.type` as `type` | +| AI Model | Resource | ai-model | ai-model (RFC #33060) | Medium | Migrate kind from Resource → ai-model | + +### Decision 3: Dry-run tool as external CLI command + +The dry-run tool runs externally against the catalog API, not as a catalog processor. It's a standalone CLI command: `npx @boost/migration-readiness --catalog-url `. + +**Why:** External CLI is simpler and safer for read-only analysis. A catalog processor hook is future work once RFCs finalize and actual migration begins. + +**How to apply:** + +- Tool queries catalog API for entities with `rhdh.io/ai-asset-category` annotation +- Outputs per-entity report: current kind/type → target RFC kind, fields needing transformation, confidence level, incompatibilities +- JSON + human-readable output formats +- CLI arguments: `--catalog-url`, `--output-format`, `--filter` + +### Decision 4: No processor extension point (yet) + +The dry-run tool does NOT use a catalog processor hook. It operates externally via catalog API queries. + +**Why:** A processor hook would execute on every entity fetch/refresh, which is unnecessary for a one-time readiness assessment. The read-only CLI approach is safer, doesn't require catalog configuration changes, and avoids polluting the catalog processing pipeline during exploratory analysis. + +**How to apply:** The tool uses Backstage's catalog client library to query entities. In a future migration implementation (post-RFC-finalization), a processor hook would make sense — but that's out of scope for this change. + +### Decision 5: Explicit "future work" framing + +The tooling scaffold and spec document are NOT the migration. The actual migration (re-mapping entities to finalized upstream kinds) is a separate future effort dependent on RFC finalization. + +**Why:** Critical for customer communication. Customers need to know: (1) RHDH is tracking upstream RFCs, (2) migration readiness is assessable today, but (3) the actual migration waits for RFC finalization to avoid churn. + +**How to apply:** + +- The specification document includes a "Future Work" section explicitly listing: actual entity migration, catalog processor for automated migration, entity kind transition plan +- The dry-run tool output includes a footer: "This is a migration-readiness assessment. Actual migration is future work pending RFC finalization." +- Customer-facing messaging emphasizes readiness assessment, not migration execution + +## Risks + +- **RFC instability:** If RFCs #32062 and #33060 change significantly, the mapping document must update. Mitigated by: confidence levels, explicit "draft mapping" framing, and living-document approach. +- **Premature migration expectations:** Customers might interpret the dry-run tool as migration execution. Mitigated by: explicit "future work" messaging in spec, tool output, and customer communication. +- **Mapping ambiguity:** Some RHDH entity types (e.g., agents, skills) don't have corresponding RFCs yet. Mitigated by: low confidence level, clear "no RFC yet" notes in mapping table, and recommendation to track future RFCs. diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md new file mode 100644 index 00000000000..7145644868e --- /dev/null +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md @@ -0,0 +1,56 @@ +# Proposal: Upstream Schema Alignment Readiness + +## Why + +Customers adopting RHDH AI Catalog early need assurance that their catalog entities won't become a dead end when upstream Backstage entity kinds stabilize. A documented mapping from current RHDH annotations to draft upstream RFC entity kinds, plus a dry-run migration-readiness tool, provides that assurance without premature migration. + +This addresses the gap between RHDH's current AI Asset annotations (`rhdh.io/ai-asset-category`, `rhdh.io/ai-asset-version`, entity `spec.type` values like `ai-agent`, `mcp-server`) and the draft Backstage RFCs that propose upstream entity kinds: + +- RFC #32062: `McpServer` entity kind +- RFC #33060: `ai-model` and `ai-model-server` entity kinds + +The mapping document and dry-run tool make the migration path transparent and measurable. Customers can enumerate their entities, see how they'd map to upstream kinds once RFCs finalize, and understand what transformations would be required — all without executing an actual migration. + +## What Boost Builds + +### Annotation Specification Document + +A formal specification covering all RHDH AI Asset annotations and entity kinds: + +- `rhdh.io/ai-asset-category` values (agent, skill, mcp-server, ai-model, model-server) +- `rhdh.io/ai-asset-version` annotation format and normalization rules +- `rhdh.io/ai-asset-source` annotation format +- Entity kind + `spec.type` mapping table showing current state → proposed RFC kind + +Explicit mapping to draft RFCs #32062 and #33060, with confidence levels per mapping (high/medium/low based on RFC stability). Published in a location accessible to platform engineers alongside existing Boost specifications. + +The actual migration is explicitly framed as future work dependent on RFC finalization. + +### Dry-Run Migration-Readiness Tooling + +A read-only CLI command that enumerates AI Asset catalog entities and reports migration readiness: + +- Queries catalog API for entities with `rhdh.io/ai-asset-category` annotation +- Generates per-entity report: current kind/type → target RFC kind, fields requiring transformation, confidence level +- Outputs both JSON (machine-readable) and human-readable formats +- Handles entities with missing/partial annotations gracefully +- No destructive changes — read-only analysis only + +The tool is a scaffold — it establishes the structure for migration-readiness assessment without executing the actual migration. + +### Current RHDH AI Asset Entity Mapping + +| AI Asset | Current Kind | Current spec.type | Target RFC Kind (draft) | Confidence | +| ------------ | ------------ | ----------------- | ---------------------------- | ---------- | +| Agent | Component | ai-agent | (no RFC yet) | Low | +| Skill | Resource | ai-skill | (no RFC yet) | Low | +| MCP Server | API | mcp-server | McpServer (RFC #32062) | Medium | +| AI Model | Resource | ai-model | ai-model (RFC #33060) | Medium | +| Model Server | Component | model-server | ai-model-server (RFC #33060) | Medium | + +## Impact + +- **Documentation:** New annotation specification document in `workspaces/boost/specifications/` directory +- **CLI Tooling:** New `@boost/migration-readiness` package with read-only catalog analysis +- **No Production Changes:** No modifications to existing catalog entities, no catalog processor changes +- **Customer Communication:** Migration-readiness assessment available, but actual migration is explicit future work diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md new file mode 100644 index 00000000000..4993cc29edc --- /dev/null +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md @@ -0,0 +1,111 @@ +# Spec: Annotation Specification Document + +**Epic:** RHIDP-15334 — Upstream Schema Alignment Readiness +**Story:** RHIDP-15346 — Annotation specification document +**Status:** 🟡 Draft + +## Overview + +Formal specification of all RHDH AI Asset annotations and entity kinds, with explicit mapping to draft Backstage RFCs #32062 (McpServer entity kind) and #33060 (ai-model/ai-model-server kinds). Published in a location accessible to platform engineers. Actual migration is explicitly framed as future work. + +--- + +## Scenario: Specification covers all rhdh.io/ai-asset-\* annotations + +**GIVEN** the RHDH AI Asset annotation scheme +**WHEN** the specification document is published +**THEN** all `rhdh.io/ai-asset-*` annotations are documented: + +- `rhdh.io/ai-asset-category` values: `agent`, `skill`, `mcp-server`, `ai-model`, `model-server` +- `rhdh.io/ai-asset-version` format and normalization rules +- `rhdh.io/ai-asset-source` format +- Each annotation includes: purpose, valid values, format constraints, examples + +--- + +## Scenario: Specification covers all spec.type values + +**GIVEN** the RHDH AI Asset entity type conventions +**WHEN** the specification document is published +**THEN** all `spec.type` values for AI Assets are documented: + +- `ai-agent` (Component kind) +- `ai-skill` (Resource kind) +- `mcp-server` (API kind) +- `ai-model` (Resource kind) +- `model-server` (Component kind) +- Each type includes: entity kind pairing, purpose, examples + +--- + +## Scenario: Mapping to RFC #32062 documented + +**GIVEN** draft Backstage RFC #32062 proposes `McpServer` entity kind +**WHEN** the specification document is published +**THEN** the mapping from RHDH MCP Server entities to RFC #32062 is documented: + +- Current: `kind: API`, `spec.type: mcp-server` +- Target: `kind: McpServer` (RFC #32062) +- Transformation requirements: migrate kind from API → McpServer +- Confidence level: Medium (RFC active but schema may evolve) +- Fields requiring transformation listed explicitly + +--- + +## Scenario: Mapping to RFC #33060 documented + +**GIVEN** draft Backstage RFC #33060 proposes `ai-model` and `ai-model-server` entity kinds +**WHEN** the specification document is published +**THEN** the mapping from RHDH AI Model and Model Server entities to RFC #33060 is documented: + +- **AI Model:** Current `kind: Resource`, `spec.type: ai-model` → Target `kind: ai-model` (RFC #33060) +- **Model Server:** Current `kind: Component`, `spec.type: model-server` → Target `kind: ai-model-server` (RFC #33060) +- Transformation requirements listed per entity type +- Confidence level: Medium (RFC active but schema may evolve) + +--- + +## Scenario: Confidence levels assigned per mapping + +**GIVEN** the draft status of upstream RFCs +**WHEN** the specification document includes entity mappings +**THEN** each mapping includes a confidence level: + +- **High:** RFC schema stable, unlikely to change +- **Medium:** RFC active, schema may evolve +- **Low:** No corresponding RFC yet, or mapping is speculative +- Confidence level rationale documented per mapping + +--- + +## Scenario: Migration explicitly framed as future work + +**GIVEN** the specification document maps current entities to draft RFC kinds +**WHEN** the document is published +**THEN** a "Future Work" section explicitly states: + +- Actual entity migration is future work +- Migration depends on RFC finalization +- The mapping document will update as RFCs evolve +- No production catalog changes are part of this specification + +--- + +## Scenario: Spec accessible to platform engineers + +**GIVEN** the annotation specification document is complete +**WHEN** it is published +**THEN** it is accessible in a location where platform engineers can find it: + +- Published in `workspaces/boost/specifications/` directory +- Follows the same structure as existing Boost specifications +- Includes a last-updated date and draft status header +- Cross-references existing `agent-creation-discovery/catalog-entities` spec + +--- + +## Scenario: Spec references existing catalog-entities entity type strategy + +**GIVEN** the `agent-creation-discovery` change defined the entity type strategy +**WHEN** the annotation specification document is written +**THEN** it cross-references the `catalog-entities/spec.md` entity type strategy table as the source of truth for current RHDH AI Asset entity mappings diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/migration-readiness-tooling/spec.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/migration-readiness-tooling/spec.md new file mode 100644 index 00000000000..31b23f4e5aa --- /dev/null +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/migration-readiness-tooling/spec.md @@ -0,0 +1,136 @@ +# Spec: Migration-Readiness Tooling Scaffold + +**Epic:** RHIDP-15334 — Upstream Schema Alignment Readiness +**Story:** RHIDP-15347 — Dry-run migration-readiness tooling scaffold +**Status:** 🟡 Draft + +## Overview + +Read-only CLI command that enumerates AI Asset catalog entities and reports migration readiness. Per-entity report shows current kind/type → target RFC kind, transformation requirements, and confidence level. No destructive changes — read-only analysis only. + +--- + +## Scenario: CLI enumerates AI Asset entities from catalog API + +**GIVEN** a Backstage catalog with AI Asset entities +**WHEN** the dry-run CLI is executed: `npx @boost/migration-readiness --catalog-url ` +**THEN** the tool: + +- Queries the catalog API for all entities +- Filters entities that have `rhdh.io/ai-asset-category` annotation +- Returns a list of AI Asset entities with their current kind and `spec.type` + +--- + +## Scenario: Per-entity mapping report generated + +**GIVEN** AI Asset entities enumerated from the catalog +**WHEN** the dry-run tool processes each entity +**THEN** a per-entity report is generated including: + +- Current entity kind and `spec.type` +- Target RFC entity kind (from annotation specification mapping) +- Confidence level (high/medium/low) +- List of fields requiring transformation +- Incompatibilities or warnings if applicable + +--- + +## Scenario: Field transformation requirements identified + +**GIVEN** an AI Asset entity with current kind/type +**WHEN** the dry-run tool analyzes the entity +**THEN** the report lists all fields that would need transformation during actual migration: + +- Kind migration (e.g., `API` → `McpServer`) +- `spec.type` remapping or removal +- Custom field migrations if required by target RFC schema +- Annotation additions/removals + +--- + +## Scenario: Confidence level reported per entity + +**GIVEN** the annotation specification defines confidence levels per mapping +**WHEN** the dry-run tool generates a per-entity report +**THEN** each entity's report includes the confidence level from the mapping specification: + +- **High:** RFC schema stable, migration path clear +- **Medium:** RFC active, schema may evolve +- **Low:** No corresponding RFC yet, mapping speculative + +--- + +## Scenario: Read-only mode (no destructive changes) + +**GIVEN** the dry-run tool is executed +**WHEN** it queries the catalog and analyzes entities +**THEN** no destructive changes occur: + +- No entity writes via catalog API +- No entity deletions +- No catalog configuration modifications +- Tool reports analysis results only + +--- + +## Scenario: JSON output format + +**GIVEN** the dry-run tool completes analysis +**WHEN** the user requests JSON output: `--output-format json` +**THEN** the tool outputs machine-readable JSON: + +```json +{ + "entities": [ + { + "name": "my-mcp-server", + "currentKind": "API", + "currentSpecType": "mcp-server", + "targetKind": "McpServer", + "confidence": "medium", + "transformations": [ + "Migrate kind: API → McpServer", + "Preserve spec.type as type field" + ] + } + ] +} +``` + +--- + +## Scenario: Human-readable output format + +**GIVEN** the dry-run tool completes analysis +**WHEN** the user requests human-readable output: `--output-format text` (default) +**THEN** the tool outputs a formatted report: + +``` +Migration Readiness Report +========================= + +Entity: my-mcp-server + Current: kind=API, spec.type=mcp-server + Target: kind=McpServer (RFC #32062) + Confidence: Medium + Transformations: + - Migrate kind: API → McpServer + - Preserve spec.type as type field + +--- +This is a migration-readiness assessment. +Actual migration is future work pending RFC finalization. +``` + +--- + +## Scenario: Handles entities with missing annotations gracefully + +**GIVEN** a catalog entity without `rhdh.io/ai-asset-category` annotation +**WHEN** the dry-run tool enumerates entities +**THEN** the entity is excluded from the report (no error thrown) + +**AND GIVEN** a catalog entity with partial annotations (e.g., missing `rhdh.io/ai-asset-version`) +**WHEN** the dry-run tool analyzes the entity +**THEN** the report includes the entity with a warning: "Partial annotations — migration may require manual review" diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/tasks.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/tasks.md new file mode 100644 index 00000000000..1de9d91b759 --- /dev/null +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/tasks.md @@ -0,0 +1,63 @@ +# Tasks: Upstream Schema Alignment Readiness + +## 1. Annotation Specification Document (P0) — RHIDP-15346 + +- [ ] 1.1 Document all `rhdh.io/ai-asset-category` values (agent, skill, mcp-server, ai-model, model-server) +- [ ] 1.2 Document `rhdh.io/ai-asset-version` annotation format and normalization rules +- [ ] 1.3 Document `rhdh.io/ai-asset-source` annotation format +- [ ] 1.4 Document entity kind + `spec.type` mapping table for all AI Asset types +- [ ] 1.5 Map each entity type to RFC #32062 (McpServer) target (if applicable) +- [ ] 1.6 Map each entity type to RFC #33060 (ai-model/ai-model-server) targets (if applicable) +- [ ] 1.7 Assign confidence levels to each mapping (high/medium/low) +- [ ] 1.8 Document fields requiring transformation per entity type +- [ ] 1.9 Add explicit "Future Work" section framing actual migration as separate effort +- [ ] 1.10 Add header with draft status and last-updated date +- [ ] 1.11 Cross-reference `agent-creation-discovery/catalog-entities` spec +- [ ] 1.12 Publish spec in `workspaces/boost/specifications/` directory + +## 2. Dry-Run Tooling Scaffold (P1) — RHIDP-15347 + +- [ ] 2.1 Create `@boost/migration-readiness` CLI package structure +- [ ] 2.2 Set up TypeScript configuration and build pipeline +- [ ] 2.3 Implement catalog API client for entity enumeration +- [ ] 2.4 Filter entities by `rhdh.io/ai-asset-category` annotation presence +- [ ] 2.5 Implement per-entity mapping logic using annotation spec rules +- [ ] 2.6 Generate per-entity report (current → target, transformations, confidence) +- [ ] 2.7 Implement JSON output formatter +- [ ] 2.8 Implement human-readable output formatter +- [ ] 2.9 Handle entities with missing `rhdh.io/ai-asset-category` annotation (exclude gracefully) +- [ ] 2.10 Handle entities with partial annotations (include with warning) +- [ ] 2.11 CLI argument parsing (`--catalog-url`, `--output-format`, `--filter`) +- [ ] 2.12 Add footer message: "This is a migration-readiness assessment. Actual migration is future work pending RFC finalization." + +## 3. Testing (P1) + +- [ ] 3.1 Unit test mapping logic with fixture entities (all five AI Asset types) +- [ ] 3.2 Unit test confidence level assignment +- [ ] 3.3 Unit test field transformation identification +- [ ] 3.4 Integration test against mock catalog API +- [ ] 3.5 Test with entities that have no AI Asset annotations (should be excluded) +- [ ] 3.6 Test with entities that have partial annotations (should include with warning) +- [ ] 3.7 Test JSON output format structure +- [ ] 3.8 Test human-readable output format rendering +- [ ] 3.9 Verify read-only: no catalog writes in any code path +- [ ] 3.10 Test CLI argument parsing and validation + +## 4. Documentation (P1) + +- [ ] 4.1 Write README for `@boost/migration-readiness` package +- [ ] 4.2 Document how to run dry-run tool (command-line usage) +- [ ] 4.3 Document output interpretation (what each field means) +- [ ] 4.4 Document confidence levels and their implications +- [ ] 4.5 Customer-facing messaging: this is readiness assessment, not migration +- [ ] 4.6 Cross-reference annotation specification document +- [ ] 4.7 Add tracking links for RFC #32062 and RFC #33060 +- [ ] 4.8 Document future work: actual migration, processor hook + +## 5. Cross-References and Dependencies (P2) + +- [ ] 5.1 Cross-reference RHDHPLAN-1507's `ai-catalog-entity-model` change +- [ ] 5.2 Cross-reference `agent-creation-discovery/catalog-entities` spec +- [ ] 5.3 Link to Backstage RFCs #32062 and #33060 +- [ ] 5.4 Cross-reference RHDHPLAN-1507 RHIDP-15302 and RHIDP-15303 (migration-readiness stories in the entity model epic) +- [ ] 5.5 Update `ai-catalog-entity-model` change to reference this specification From 6937d8a8e4a5ac5cdaa12f2ab4ce4789ff8b0cb2 Mon Sep 17 00:00:00 2001 From: gabemontero Date: Wed, 8 Jul 2026 18:33:21 -0400 Subject: [PATCH 02/19] fix(boost): RHDHPLAN-1513 openspec consolidation for RHIDP-15333 closure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RHIDP-15333 (Ingestion Audit Logging & Metrics) closed and absorbed by RHIDP-15277 (AI Catalog RBAC Audit Logging, RHDHPLAN-1508). Closed epic openspec (ingestion-audit-metrics/): - .openspec.yaml status → consolidated with absorbing epic metadata - proposal.md, design.md, tasks.md: consolidation headers with story mapping - specs/audit-events: status → Consolidated into RHIDP-15280 - specs/analytics-api: status → Consolidated into RHIDP-15277 - specs/eval-hub-integration: status → Consolidated into RHIDP-15277 Surviving epic openspecs (consolidation cross-references): - ingestion-health-dashboard: proposal.md + design.md annotations - connector-config-hot-reload: proposal.md + design.md annotations - upstream-schema-alignment: proposal.md + design.md annotations Jira updates: - RHIDP-15343 → CONSOLIDATED into RHIDP-15280, closed as Duplicate - RHIDP-15344 → CONSOLIDATED into RHIDP-15277 scope, closed as Duplicate - RHIDP-15345 → CONSOLIDATED into RHIDP-15277 scope, closed as Duplicate - Blocks links: RHIDP-15295→15338, RHIDP-15258→15346, RHIDP-15258→15347 Co-Authored-By: Claude Opus 4.6 Signed-off-by: gabemontero --- .../openspec/changes/connector-config-hot-reload/design.md | 2 ++ .../changes/connector-config-hot-reload/proposal.md | 2 ++ .../changes/ingestion-audit-metrics/.openspec.yaml | 7 ++++++- .../openspec/changes/ingestion-audit-metrics/design.md | 2 ++ .../openspec/changes/ingestion-audit-metrics/proposal.md | 2 ++ .../ingestion-audit-metrics/specs/analytics-api/spec.md | 7 ++++--- .../ingestion-audit-metrics/specs/audit-events/spec.md | 7 ++++--- .../specs/eval-hub-integration/spec.md | 7 ++++--- .../openspec/changes/ingestion-audit-metrics/tasks.md | 2 ++ .../openspec/changes/ingestion-health-dashboard/design.md | 2 ++ .../changes/ingestion-health-dashboard/proposal.md | 2 ++ .../openspec/changes/upstream-schema-alignment/design.md | 2 ++ .../openspec/changes/upstream-schema-alignment/proposal.md | 2 ++ 13 files changed, 36 insertions(+), 10 deletions(-) diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md index da8a56226b2..74e632d53a2 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md @@ -2,6 +2,8 @@ ## Context +> **RHDHPLAN-1513 Consolidation (2026-07-08):** Epic RHIDP-15333 (Ingestion Audit Logging & Metrics) was closed and consolidated into RHIDP-15277 (RHDHPLAN-1508). This hot-reload epic is unaffected. Config change audit events (originally RHIDP-15333 scope) are now under RHIDP-15277/RHIDP-15280 — connector config changes should emit `ingestion.config.change` audit events using the RHIDP-15277 structured JSON pattern. + Boost's `RuntimeConfigResolver` provides a two-layer config model: YAML baseline (read-only, deployment-time) + database overrides (mutable, runtime). The resolver caches the merged config with 30-second TTL and immediate invalidation on DB writes. This change extends that proven infrastructure to connector configuration. Backstage's built-in `ConfigApi` loads YAML at startup with no hot-reload. Boost's `RuntimeConfigResolver` is a **custom layer** built on top of `ConfigApi` that adds: diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md index cb55a47cced..cb971b42acf 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md @@ -2,6 +2,8 @@ ## Why +> **RHDHPLAN-1513 Consolidation (2026-07-08):** Epic RHIDP-15333 (Ingestion Audit Logging & Metrics) was closed — its scope has been absorbed by RHIDP-15277 (AI Catalog RBAC Audit Logging) under RHDHPLAN-1508. This hot-reload epic (RHIDP-15332) is unaffected — it remains a surviving RHDHPLAN-1513 epic alongside RHIDP-15331 (Ingestion Health Dashboard) and RHIDP-15334 (Upstream Schema Alignment). Config change audit events (originally RHIDP-15333 scope) are now delivered under RHIDP-15277/RHIDP-15280 — connector config changes should emit audit events using the RHIDP-15277 pattern. + Connectors need configuration changes without pod restart. Toggling a connector on/off, changing sync schedules, or updating endpoint URLs currently requires editing YAML config and redeploying — a multi-minute cycle that blocks quick experimentation and rapid incident response. Enterprise customers need immediate control over ingestion behavior without downtime. Boost's existing `RuntimeConfigResolver` already solves this for core boost settings: it provides a two-layer config model (YAML baseline + database overrides) with 30-second TTL refresh. This change extends that proven infrastructure to connector settings, enabling hot-reload of connector enable/disable, endpoint URLs, and sync schedules. diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/.openspec.yaml b/workspaces/boost/openspec/changes/ingestion-audit-metrics/.openspec.yaml index 9df4af2b637..d8f5d56b7bf 100644 --- a/workspaces/boost/openspec/changes/ingestion-audit-metrics/.openspec.yaml +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/.openspec.yaml @@ -1,3 +1,8 @@ schema: spec-driven created: 2026-07-08 -status: draft +status: consolidated +consolidation: + date: 2026-07-08 + absorbed_by: RHIDP-15277 + plan: RHDHPLAN-1508 + reason: Ingestion audit logging scope consolidated into RBAC Audit Logging epic to avoid parallel audit implementations diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md index 31620b93f6d..f478e8fe05a 100644 --- a/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md @@ -2,6 +2,8 @@ ## Context +> **RHDHPLAN-1513 Consolidation (2026-07-08):** This entire change has been consolidated into RHIDP-15277 (AI Catalog RBAC Audit Logging, RHDHPLAN-1508). The audit event infrastructure, analytics REST API, and Eval Hub integration described here are now delivered under RHIDP-15277's expanded scope. Specifically: sync audit events → RHIDP-15280, analytics metrics API → RHIDP-15277 epic scope, Eval Hub integration → RHIDP-15277 epic scope. + Boost audit logging extends the pattern established by RHIDP-15277 (RBAC audit events). That epic defined the RHDH audit log integration, structured JSON event schema, and event emission infrastructure. This change applies the same pattern to ingestion operations. Analytics REST API provides the data layer for the Admin Panel's Analytics tab (RHDHPLAN-1509). The tab consumes sync history, quality scores, and match coverage metrics via RBAC-gated endpoints. diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/proposal.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/proposal.md index 7b25a7ce3e4..e9fcb9ac72a 100644 --- a/workspaces/boost/openspec/changes/ingestion-audit-metrics/proposal.md +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/proposal.md @@ -2,6 +2,8 @@ ## Why +> **RHDHPLAN-1513 Consolidation (2026-07-08):** Epic RHIDP-15333 (Ingestion Audit Logging & Metrics) was closed — its scope has been absorbed by RHIDP-15277 (AI Catalog RBAC Audit Logging) under RHDHPLAN-1508. RHDHPLAN-1513 continues with 3 surviving epics: RHIDP-15331 (Ingestion Health Dashboard), RHIDP-15332 (Connector Config Hot-Reload), and RHIDP-15334 (Upstream Schema Alignment). Orphaned stories: RHIDP-15343 → RHIDP-15280, RHIDP-15344 → RHIDP-15277 scope, RHIDP-15345 → RHIDP-15277 scope. + Compliance requires an audit trail of ingestion operations. Every sync attempt, every config change, and every quality score must be recorded with timestamps, actors, and outcomes. The Analytics tab needs historical sync data, quality distributions, and match coverage metrics to give admins visibility into the AI system's health and evolution. Eval Hub integration closes the quality feedback loop — scores from LightEval, IBM Clear, and GuideLLM flow into the analytics API and surface in the admin UI. Without this loop, quality is invisible; with it, teams can track quality degradation over time and debug skill performance regressions. diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/analytics-api/spec.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/analytics-api/spec.md index d3d90414b4e..1448b30aae9 100644 --- a/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/analytics-api/spec.md +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/analytics-api/spec.md @@ -1,8 +1,9 @@ # Spec: Analytics Metrics REST API -> **Status:** ✅ READY -> **Story:** RHIDP-15344 -> **Coverage:** Sync history endpoint, quality scores endpoint, match coverage endpoint, Neo4j sync status, RBAC gating, pagination, date range filtering +> **Status:** 🔀 CONSOLIDATED into RHIDP-15277 (RHDHPLAN-1508) +> **Story:** RHIDP-15344 (Closed — absorbed by RHIDP-15277 epic scope) +> **Coverage:** Sync history endpoint, quality scores endpoint, match coverage endpoint, Neo4j sync status, RBAC gating, pagination, date range filtering +> **Consolidation (2026-07-08):** Analytics metrics REST API scope is now part of the expanded RHIDP-15277 epic (AI Catalog RBAC Audit Logging, RHDHPLAN-1508), which includes RBAC-gated analytics data exposure alongside audit logging. ## Scenarios diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/audit-events/spec.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/audit-events/spec.md index 5fdd75bb0ab..0f84db34290 100644 --- a/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/audit-events/spec.md +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/audit-events/spec.md @@ -1,8 +1,9 @@ # Spec: Ingestion Sync Audit Events -> **Status:** ✅ READY -> **Story:** RHIDP-15343 -> **Coverage:** Audit event emission for sync lifecycle, config changes, RHDH audit log integration, disconnected cluster support +> **Status:** 🔀 CONSOLIDATED into RHIDP-15280 (RHDHPLAN-1508) +> **Story:** RHIDP-15343 (Closed — absorbed by RHIDP-15280) +> **Coverage:** Audit event emission for sync lifecycle, config changes, RHDH audit log integration, disconnected cluster support +> **Consolidation (2026-07-08):** Ingestion sync audit events are now delivered under RHIDP-15280 (Emit audit events for entity provider ingestion sync cycles) within RHIDP-15277 (AI Catalog RBAC Audit Logging, RHDHPLAN-1508). ## Scenarios diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/eval-hub-integration/spec.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/eval-hub-integration/spec.md index 4a900275d87..0c0ae6efde9 100644 --- a/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/eval-hub-integration/spec.md +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/eval-hub-integration/spec.md @@ -1,8 +1,9 @@ # Spec: Eval Hub Integration for Skill Quality Scores -> **Status:** ✅ READY -> **Story:** RHIDP-15345 -> **Coverage:** Eval Hub endpoint configuration, quality score ingestion, per-skill score storage, aggregate distribution computation, graceful handling when Eval Hub unavailable, multiple eval backend support, score refresh cycle +> **Status:** 🔀 CONSOLIDATED into RHIDP-15277 (RHDHPLAN-1508) +> **Story:** RHIDP-15345 (Closed — absorbed by RHIDP-15277 epic scope) +> **Coverage:** Eval Hub endpoint configuration, quality score ingestion, per-skill score storage, aggregate distribution computation, graceful handling when Eval Hub unavailable, multiple eval backend support, score refresh cycle +> **Consolidation (2026-07-08):** Eval Hub quality score integration is now part of the expanded RHIDP-15277 epic (AI Catalog RBAC Audit Logging, RHDHPLAN-1508). Eval Hub API stability remains the primary integration risk. ## Scenarios diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/tasks.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/tasks.md index 585966b0771..17934ea5bda 100644 --- a/workspaces/boost/openspec/changes/ingestion-audit-metrics/tasks.md +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/tasks.md @@ -1,5 +1,7 @@ # Tasks: Ingestion Audit Logging and Metrics +> **RHDHPLAN-1513 Consolidation (2026-07-08):** All tasks below have been consolidated into RHIDP-15277 (AI Catalog RBAC Audit Logging, RHDHPLAN-1508). Task groups 1-4 (audit events) → RHIDP-15280, task groups 5-10 (analytics API, Neo4j status) → RHIDP-15277 epic scope, task groups 11-13 (Eval Hub) → RHIDP-15277 epic scope. Cross-reference tasks (group 17) remain relevant — surviving RHDHPLAN-1513 epics should reference RHIDP-15277 for audit event patterns. + ## 1. Audit Event Infrastructure (P0) — RHIDP-15343 - [ ] 1.1 Define ingestion audit event types in `plugins/boost-backend/src/audit/event-types.ts` diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md index d37c1257eac..f7258319dd0 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md @@ -2,6 +2,8 @@ ## Context +> **RHDHPLAN-1513 Consolidation (2026-07-08):** Epic RHIDP-15333 (Ingestion Audit Logging & Metrics) was closed and consolidated into RHIDP-15277 (RHDHPLAN-1508). This health dashboard is unaffected — audit event patterns referenced in this design continue to come from RHIDP-15277. The Neo4j sync panel (RHIDP-15338) depends on RHIDP-15295 (RHDHPLAN-1507 Neo4j Knowledge Graph Sync Adapter) being implemented first. + Boost implements the ingestion health dashboard as a standard admin panel section following the existing patterns established in the augment workspace's admin panel (model connection, system prompt, agent config sections). The augment reference prototype has no equivalent feature — this is net-new functionality for Boost. The design follows RHDH Extensions Catalog patterns for admin tooling: backend exposes REST API, frontend renders via PatternFly components, RBAC enforced at API layer. ## Goals diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/proposal.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/proposal.md index 1d58681fa66..fa9b5f67276 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/proposal.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/proposal.md @@ -2,6 +2,8 @@ ## Why +> **RHDHPLAN-1513 Consolidation (2026-07-08):** Epic RHIDP-15333 (Ingestion Audit Logging & Metrics) was closed — its scope has been absorbed by RHIDP-15277 (AI Catalog RBAC Audit Logging) under RHDHPLAN-1508. This health dashboard epic (RHIDP-15331) is unaffected — it remains a surviving RHDHPLAN-1513 epic alongside RHIDP-15332 (Connector Config Hot-Reload) and RHIDP-15334 (Upstream Schema Alignment). Audit event patterns referenced here continue to come from RHIDP-15277. The Neo4j sync panel (RHIDP-15338) depends on RHIDP-15295 (RHDHPLAN-1507 Neo4j Knowledge Graph Sync Adapter). + Admins need visibility into connector health without reading raw logs. When catalog connectors fail, administrators face: - **Silent failures**: Connectors fail but no alerts fire — catalog goes stale, users see outdated data diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/design.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/design.md index 4dae3adf327..465527607c0 100644 --- a/workspaces/boost/openspec/changes/upstream-schema-alignment/design.md +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/design.md @@ -2,6 +2,8 @@ ## Context +> **RHDHPLAN-1513 Consolidation (2026-07-08):** Epic RHIDP-15333 (Ingestion Audit Logging & Metrics) was closed and consolidated into RHIDP-15277 (RHDHPLAN-1508). This schema alignment epic is unaffected. The annotation scheme formalized here depends on RHDHPLAN-1507's Entity-Provider SDK (RHIDP-15258), which now includes annotation definitions after consolidation. + RHDH Boost has defined a set of AI Asset annotations and entity type conventions to represent agents, skills, MCP servers, AI models, and model servers in the Backstage catalog. These annotations (`rhdh.io/ai-asset-category`, `rhdh.io/ai-asset-version`) and `spec.type` values (`ai-agent`, `mcp-server`, etc.) are documented in the `agent-creation-discovery` change's `catalog-entities/spec.md`. Meanwhile, upstream Backstage has draft RFCs proposing new entity kinds: diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md index 7145644868e..47da2c53b8d 100644 --- a/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md @@ -2,6 +2,8 @@ ## Why +> **RHDHPLAN-1513 Consolidation (2026-07-08):** Epic RHIDP-15333 (Ingestion Audit Logging & Metrics) was closed — its scope has been absorbed by RHIDP-15277 (AI Catalog RBAC Audit Logging) under RHDHPLAN-1508. This schema alignment epic (RHIDP-15334) is unaffected — it remains a surviving RHDHPLAN-1513 epic alongside RHIDP-15331 (Ingestion Health Dashboard) and RHIDP-15332 (Connector Config Hot-Reload). The annotation specification (RHIDP-15346) depends on RHDHPLAN-1507's Entity-Provider SDK (RHIDP-15258) which now includes the annotation scheme after consolidation. + Customers adopting RHDH AI Catalog early need assurance that their catalog entities won't become a dead end when upstream Backstage entity kinds stabilize. A documented mapping from current RHDH annotations to draft upstream RFC entity kinds, plus a dry-run migration-readiness tool, provides that assurance without premature migration. This addresses the gap between RHDH's current AI Asset annotations (`rhdh.io/ai-asset-category`, `rhdh.io/ai-asset-version`, entity `spec.type` values like `ai-agent`, `mcp-server`) and the draft Backstage RFCs that propose upstream entity kinds: From 3d2ce194b28f1c6385ae205cba25d6ed16755aa8 Mon Sep 17 00:00:00 2001 From: gabemontero Date: Tue, 14 Jul 2026 16:35:42 -0400 Subject: [PATCH 03/19] fix(boost): RHDHPLAN-1513 align skill entity mapping to AIResource/skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rebase consistency fix: update stale skill entity references to match confirmed category taxonomy (skill → AIResource kind, spec.type: skill). - proposal.md: Skill row Resource/ai-skill → AIResource/skill - annotation-specification/spec.md: ai-skill (Resource kind) → skill (AIResource kind) - ingestion-audit-metrics/design.md: spec.type: ai-skill → spec.type: skill (2 locations) Co-Authored-By: Claude Opus 4.6 --- .../boost/openspec/changes/ingestion-audit-metrics/design.md | 4 ++-- .../openspec/changes/upstream-schema-alignment/proposal.md | 2 +- .../specs/annotation-specification/spec.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md index f478e8fe05a..d2d17051165 100644 --- a/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md @@ -191,12 +191,12 @@ Match coverage compares declared agent capabilities (from Kagenti agent specs) a Agent capabilities: Declared in Kagenti agent specs (e.g., `metadata.capabilities: ['code-review', 'bug-detection']`). Boost reads agent entities from catalog. -Skill catalog: Entities with `spec.type: ai-skill` (from RHDHPLAN-1506 skill ingestion). Boost reads skill entities from catalog. +Skill catalog: Entities with `spec.type: skill` (from RHDHPLAN-1506 skill ingestion). Boost reads skill entities from catalog. Match logic: 1. Fetch all agent entities (filter by `spec.type: ai-agent` or similar) -2. Fetch all skill entities (filter by `spec.type: ai-skill`) +2. Fetch all skill entities (filter by `spec.type: skill`) 3. Extract capabilities from agents: `agent.metadata.capabilities` 4. Extract skill IDs from skills: `skill.metadata.id` 5. Compute matched capabilities: capabilities that have a corresponding skill diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md index 47da2c53b8d..05c205db6ec 100644 --- a/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md @@ -45,7 +45,7 @@ The tool is a scaffold — it establishes the structure for migration-readiness | AI Asset | Current Kind | Current spec.type | Target RFC Kind (draft) | Confidence | | ------------ | ------------ | ----------------- | ---------------------------- | ---------- | | Agent | Component | ai-agent | (no RFC yet) | Low | -| Skill | Resource | ai-skill | (no RFC yet) | Low | +| Skill | AIResource | skill | (no RFC yet) | Low | | MCP Server | API | mcp-server | McpServer (RFC #32062) | Medium | | AI Model | Resource | ai-model | ai-model (RFC #33060) | Medium | | Model Server | Component | model-server | ai-model-server (RFC #33060) | Medium | diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md index 4993cc29edc..b07ad900e8d 100644 --- a/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md @@ -30,7 +30,7 @@ Formal specification of all RHDH AI Asset annotations and entity kinds, with exp **THEN** all `spec.type` values for AI Assets are documented: - `ai-agent` (Component kind) -- `ai-skill` (Resource kind) +- `skill` (AIResource kind) - `mcp-server` (API kind) - `ai-model` (Resource kind) - `model-server` (Component kind) From b43cbe28fce7d0c8edabe32a6a2f022b56a7981b Mon Sep 17 00:00:00 2001 From: gabemontero Date: Tue, 14 Jul 2026 16:38:25 -0400 Subject: [PATCH 04/19] fix(boost): RHDHPLAN-1513 address fullsend review findings across openspecs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses all 9 findings from fullsend review agent on PR #3755: 1. Credential rotation latency: fix ≤90s claim → ~6 minutes (60s kubelet + 5min reconciliation) in design.md Decision 4 and Risks section 2. API endpoint pattern: standardize on POST /api/boost/admin/config with key-value body; fix missing /api/boost prefix in toggle scenarios 3. Disabled connector filtering: reconcile design.md with spec — excluded by default, available via ?includeDisabled=true 4. PostgreSQL INDEX syntax: replace MySQL-style inline INDEX with separate CREATE INDEX statements in both design.md files 5. Health derivation gap: redefine as Healthy/Degraded/Failing/Unknown covering all attempt combinations including [success, success, failure] 6. Fewer-than-3 edge cases: explicit derivation for 0-2 attempts using same rules as 3-attempt case 7. Missing tls.caFile: add to Jira connector config schema in spec 8. Orphaned db-only configScope: remove from proposal.md 9. Propagation messages: fix 'within 30 seconds' → 'within 30 seconds + next reconciliation cycle' Co-Authored-By: Claude Opus 4.6 Signed-off-by: gabemontero --- .../connector-config-hot-reload/design.md | 10 +++++----- .../connector-config-hot-reload/proposal.md | 2 +- .../specs/config-admin-ui/spec.md | 12 ++++++------ .../specs/config-schemas/spec.md | 4 ++-- .../changes/ingestion-audit-metrics/design.md | 7 ++++--- .../changes/ingestion-health-dashboard/design.md | 16 ++++++++++------ .../specs/health-status-api/spec.md | 13 +++++++------ 7 files changed, 35 insertions(+), 29 deletions(-) diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md index 74e632d53a2..247110dc07b 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md @@ -101,7 +101,7 @@ Entity providers receive config changes on next reconciliation cycle. Provider's ### Decision 4: Credential rotation handling -K8s Secret mount propagation can take up to 60s for projected volumes (kubelet sync period). Provider re-reads mounted file each cycle. Effective credential rotation = mount propagation delay (≤60s) + resolver TTL (30s) = ≤90s worst case. +K8s Secret mount propagation can take up to 60s for projected volumes (kubelet sync period). Provider re-reads mounted file each reconciliation cycle. Effective credential rotation = mount propagation delay (≤60s) + reconciliation interval (up to 5m) = ~6 minutes worst case. **Why not watch Secret mounts:** Filesystem watching (inotify) adds complexity and failure modes (missed events, watcher exhaustion). Reading the mounted Secret file at each reconciliation cycle is simpler and sufficient for credential rotation use cases. @@ -113,7 +113,7 @@ K8s Secret mount propagation can take up to 60s for projected volumes (kubelet s 4. Provider reads mounted Secret file 5. Provider uses new credentials for sync -**Why not immediate propagation:** Credentials are long-lived secrets (API tokens, certificates). 90s rotation latency is acceptable. Immediate propagation would require filesystem watchers or polling loops, adding complexity for minimal benefit. +**Why not immediate propagation:** Credentials are long-lived secrets (API tokens, certificates). ~6 minute rotation latency is acceptable. Immediate propagation would require filesystem watchers or polling loops, adding complexity for minimal benefit. ### Decision 5: Admin UI writes DB overrides via AdminConfigService @@ -126,9 +126,9 @@ Admin UI writes connector config changes via `AdminConfigService` — same patte 1. Admin opens connector config section 2. Form fields pre-populated with current merged config (YAML baseline + DB overrides) 3. Admin toggles `enabled` or changes `endpoint` -4. Frontend calls `POST /admin/config/connectors.jira` with new values +4. Frontend calls `POST /api/boost/admin/config` with `{ key: "connectors.jira", value: {...} }` 5. Backend validates via Zod schema, writes DB override, invalidates cache -6. Frontend shows immediate visual feedback ("Saved — will take effect within 30s") +6. Frontend shows immediate visual feedback ("Saved — will take effect within 30s + next reconciliation cycle") **YAML-only fields (read-only in UI):** TLS mount paths, Secret references shown as read-only info. Tooltip: "Deployment-time config. Edit YAML to change." @@ -136,6 +136,6 @@ Admin UI writes connector config changes via `AdminConfigService` — same patte ## Risks -- **K8s Secret mount propagation delays:** Mitigated by documenting 90s worst-case credential rotation latency. For emergency credential rotation, admin can manually restart connector pod. +- **K8s Secret mount propagation delays:** Mitigated by documenting ~6 minute worst-case credential rotation latency (60s kubelet sync + 5m reconciliation interval). For emergency credential rotation, admin can manually restart connector pod. - **Config schema versioning:** Mitigated by Zod schema versioning. Breaking changes require migration logic in `RuntimeConfigResolver`. - **Partial config state during cache refresh:** Mitigated by atomic cache updates. Resolver fetches full merged config (YAML + DB) before updating cache entry. diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md index cb971b42acf..69de26564b7 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md @@ -15,7 +15,7 @@ The key distinction: Backstage's built-in `ConfigApi` loads config at startup wi ### Config Schemas - Zod schema definitions for per-connector settings: `enabled`, `endpoint`, `schedule`, `tls`, `credentials` -- `configScope` annotation on each field: `yaml-only` (mount paths, K8s Secret references), `db-overridable` (enable/disable, endpoint URL, schedule), `db-only` (runtime-only state) +- `configScope` annotation on each field: `yaml-only` (mount paths, K8s Secret references), `db-overridable` (enable/disable, endpoint URL, schedule) - Schema validation rejects invalid connector config values before write - Integration with `RuntimeConfigResolver`'s two-layer resolution diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md index 570df09d85d..6a5240035c3 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md @@ -20,15 +20,15 @@ Admin UI provides toggle controls for enabling/disabling connectors. #### Scenario: Toggle connector off - **WHEN** admin clicks toggle to disable Jira connector -- **THEN** frontend calls `POST /admin/config/connectors.jira` with `{ enabled: false }` +- **THEN** frontend calls `POST /api/boost/admin/config` with `{ key: "connectors.jira", value: { enabled: false } }` - **AND** backend validates via Zod schema, writes DB override, invalidates cache -- **AND** frontend shows immediate visual feedback: "Saved — will take effect within 30 seconds" +- **AND** frontend shows immediate visual feedback: "Saved — will take effect within 30 seconds + next reconciliation cycle" - **AND** toggle UI updates to show "Disabled" state #### Scenario: Toggle connector on - **WHEN** admin clicks toggle to enable previously disabled Jira connector -- **THEN** frontend calls `POST /admin/config/connectors.jira` with `{ enabled: true }` +- **THEN** frontend calls `POST /api/boost/admin/config` with `{ key: "connectors.jira", value: { enabled: true } }` - **AND** backend writes DB override, invalidates cache - **AND** frontend shows immediate visual feedback - **AND** toggle UI updates to show "Enabled" state @@ -48,7 +48,7 @@ Admin UI provides form fields for endpoint URL and sync schedule configuration. - **WHEN** admin edits endpoint URL to `https://jira-staging.example.com` and saves - **THEN** frontend validates URL format before submitting -- **AND** frontend calls `POST /admin/config/connectors.jira` with `{ endpoint: "https://jira-staging.example.com" }` +- **AND** frontend calls `POST /api/boost/admin/config` with `{ key: "connectors.jira", value: { endpoint: "https://jira-staging.example.com" } }` - **AND** backend validates, writes DB override, invalidates cache - **AND** UI shows: "Saved — new endpoint will be used on next sync (within schedule interval)" @@ -61,7 +61,7 @@ Admin UI provides form fields for endpoint URL and sync schedule configuration. #### Scenario: Change schedule interval - **WHEN** admin changes schedule from "5 minutes" to "10 minutes" and saves -- **THEN** frontend calls `POST /admin/config/connectors.jira` with `{ schedule: { intervalMs: 600000 } }` +- **THEN** frontend calls `POST /api/boost/admin/config` with `{ key: "connectors.jira", value: { schedule: { intervalMs: 600000 } } }` - **AND** backend validates, writes DB override, invalidates cache - **AND** UI shows: "Saved — new schedule will take effect on next cycle" @@ -139,7 +139,7 @@ Admin UI provides immediate validation feedback before and after save. - **WHEN** admin saves valid config change - **THEN** UI displays success notification: "Connector config saved successfully" -- **AND** notification includes propagation info: "Changes will take effect within 30 seconds" +- **AND** notification includes propagation info: "Changes will take effect within 30 seconds + next reconciliation cycle" - **AND** notification auto-dismisses after 5 seconds ### Requirement: RBAC Gating diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md index f5af37c1660..0fa53b764f0 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md @@ -13,8 +13,8 @@ Each connector has a Zod schema defining all configuration fields with `configSc #### Scenario: Jira connector config schema - **WHEN** Jira connector config schema is defined -- **THEN** schema includes fields: `enabled` (boolean), `endpoint` (URL string), `schedule.intervalMs` (number), `schedule.cron` (string), `credentials.secretRef` (string), `credentials.secretKey` (string), `namespace` (string), `batchSize` (number), `timeout.connectionMs` (number) -- **AND** each field is annotated with `configScope`: `enabled` is `db-overridable`, `endpoint` is `db-overridable`, `schedule.intervalMs` is `db-overridable`, `schedule.cron` is `db-overridable`, `credentials.secretRef` is `yaml-only`, `credentials.secretKey` is `yaml-only`, `namespace` is `yaml-only`, `batchSize` is `db-overridable`, `timeout.connectionMs` is `db-overridable` +- **THEN** schema includes fields: `enabled` (boolean), `endpoint` (URL string), `schedule.intervalMs` (number), `schedule.cron` (string), `tls.caFile` (string), `credentials.secretRef` (string), `credentials.secretKey` (string), `namespace` (string), `batchSize` (number), `timeout.connectionMs` (number) +- **AND** each field is annotated with `configScope`: `enabled` is `db-overridable`, `endpoint` is `db-overridable`, `schedule.intervalMs` is `db-overridable`, `schedule.cron` is `db-overridable`, `tls.caFile` is `yaml-only`, `credentials.secretRef` is `yaml-only`, `credentials.secretKey` is `yaml-only`, `namespace` is `yaml-only`, `batchSize` is `db-overridable`, `timeout.connectionMs` is `db-overridable` #### Scenario: GitHub connector config schema diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md index d2d17051165..e5c2e9f07b7 100644 --- a/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md @@ -129,10 +129,11 @@ CREATE TABLE boost_quality_scores ( eval_source VARCHAR(100) NOT NULL, -- 'lighteval', 'ibm-clear', 'guidellm', etc. score DECIMAL(5, 3) NOT NULL, -- 0.000 to 1.000 (normalized) timestamp TIMESTAMP NOT NULL DEFAULT NOW(), - metadata JSONB, -- extensible for eval-framework-specific data - INDEX idx_skill_ref (skill_entity_ref), - INDEX idx_timestamp (timestamp DESC) + metadata JSONB -- extensible for eval-framework-specific data ); + +CREATE INDEX idx_quality_scores_skill_ref ON boost_quality_scores (skill_entity_ref); +CREATE INDEX idx_quality_scores_timestamp ON boost_quality_scores (timestamp DESC); ``` Aggregate distribution: Computed on-demand when `GET /api/boost/admin/analytics/quality-scores` is called. Query groups scores into buckets (e.g., 0.0-0.2, 0.2-0.4, ..., 0.8-1.0) and returns histogram. diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md index f7258319dd0..3885294855e 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md @@ -40,16 +40,20 @@ CREATE TABLE sync_attempts ( assets_added INTEGER NOT NULL DEFAULT 0, assets_updated INTEGER NOT NULL DEFAULT 0, assets_removed INTEGER NOT NULL DEFAULT 0, - duration_ms INTEGER NOT NULL, - INDEX (connector_id, timestamp DESC) + duration_ms INTEGER NOT NULL ); + +CREATE INDEX idx_sync_attempts_connector_ts ON sync_attempts (connector_id, timestamp DESC); ``` Health status is derived from the last N sync attempts (default N=3): -- **Healthy**: Last 3 sync attempts succeeded -- **Degraded**: Last sync attempt succeeded but 1+ failures in last 3 attempts -- **Failing**: Last 3 sync attempts failed +- **Healthy**: All N attempts succeeded +- **Degraded**: Mixed results (not all-success and not all-failure) +- **Failing**: All N attempts failed +- **Unknown**: Zero sync attempts recorded (new connector, never synced) + +If fewer than N attempts exist, the same rules apply to the available attempts. **Why:** Storing sync attempts provides historical context for health trends. Derivation logic (last N attempts) balances recency with resilience against transient failures. Separate table avoids coupling health storage to connector config schema. @@ -94,7 +98,7 @@ The health UI distinguishes between three connector states: 2. **Enabled + Failing**: Red status badge, error summary, diagnostic guidance, "Force Sync" action 3. **Disabled**: Grey/muted status badge, no error indicators, shows "Disabled" label, no "Force Sync" action -Disabled state is set via connector config (`enabled: false`). Health status derivation skips disabled connectors entirely — they don't generate health records or consume health API response space. +Disabled state is set via connector config (`enabled: false`). Health status derivation skips disabled connectors by default — they are excluded from the API response unless `?includeDisabled=true` is set. When included, disabled connectors render with muted treatment in the UI (grey badge, no error indicators). **Why:** Air-gapped deployments intentionally disable upstream connectors (GitHub, GitLab) but enable internal ones (Jira). Showing disabled connectors as "failing" generates false alarms. Muted treatment signals "expected state, no action needed." diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/health-status-api/spec.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/health-status-api/spec.md index 35ee5a0e8c7..7d0f9707c80 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/health-status-api/spec.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/health-status-api/spec.md @@ -19,12 +19,13 @@ The API exposes connector health state for admin dashboard consumption. #### Scenario: Health status derivation logic -- **WHEN** a connector has 3+ sync attempts in the database -- **THEN** the API derives status from the last 3 attempts: - - **Healthy** if all 3 succeeded - - **Degraded** if the most recent succeeded but at least 1 of the prior 2 failed - - **Failing** if all 3 failed -- **AND** if fewer than 3 attempts exist, status is derived from available attempts (e.g., 1 success = Healthy) +- **WHEN** a connector has 1+ sync attempts in the database +- **THEN** the API derives status from the last 3 attempts (or fewer if less than 3 exist): + - **Healthy** if all attempts succeeded + - **Degraded** if results are mixed (not all-success and not all-failure) + - **Failing** if all attempts failed + - **Unknown** if zero sync attempts exist (new connector, never synced) +- **AND** the same derivation rules apply regardless of whether 1, 2, or 3 attempts are available #### Scenario: Empty state for zero connectors From 957f5b06dd8703dcd36a1861251d5ea758a74ade Mon Sep 17 00:00:00 2001 From: gabemontero Date: Thu, 16 Jul 2026 17:58:14 -0400 Subject: [PATCH 05/19] fix(boost): RHDHPLAN-1513 document db-only configScope for runtime-only state Re-add db-only to proposal.md configScope list (reverses earlier removal). Add concrete db-only fields (lastSyncTimestamp, lastSyncOutcome, runStatus) to design.md Decision 2 table with rationale paragraph explaining these are pure runtime state with no YAML baseline. Co-Authored-By: Claude Opus 4.6 Signed-off-by: gabemontero --- .../openspec/changes/connector-config-hot-reload/design.md | 5 +++++ .../openspec/changes/connector-config-hot-reload/proposal.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md index 247110dc07b..1eb9039932c 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md @@ -77,6 +77,11 @@ Each connector config field is annotated with `configScope` to control which lay | `namespace` | `yaml-only` | Namespace is deployment-time config (can't change active provider's target namespace without restart) | | `batchSize` | `db-overridable` | Admin can tune performance at runtime | | `timeout.connectionMs` | `db-overridable` | Admin can adjust for network conditions at runtime | +| `lastSyncTimestamp` | `db-only` | Runtime state written by provider after sync — no YAML baseline exists | +| `lastSyncOutcome` | `db-only` | Runtime state (success/failure) — written by provider, not configurable | +| `runStatus` | `db-only` | Transient state (running/idle) — no deployment-time equivalent | + +**Why db-only exists:** Some fields are pure runtime state — they are written by the system during operation and have no YAML baseline or admin-configurable equivalent. They live exclusively in the database and are never merged with YAML config. The `RuntimeConfigResolver` returns them as-is from the DB layer without two-layer merging. **Why not make everything db-overridable:** Mount paths and Secret references can't change at runtime without a pod restart. Making them `db-overridable` would create false expectations of hot-reload capability. diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md index 69de26564b7..bbe6c005bdf 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md @@ -15,7 +15,7 @@ The key distinction: Backstage's built-in `ConfigApi` loads config at startup wi ### Config Schemas - Zod schema definitions for per-connector settings: `enabled`, `endpoint`, `schedule`, `tls`, `credentials` -- `configScope` annotation on each field: `yaml-only` (mount paths, K8s Secret references), `db-overridable` (enable/disable, endpoint URL, schedule) +- `configScope` annotation on each field: `yaml-only` (mount paths, K8s Secret references), `db-overridable` (enable/disable, endpoint URL, schedule), `db-only` (runtime-only state — last sync timestamp, run status) - Schema validation rejects invalid connector config values before write - Integration with `RuntimeConfigResolver`'s two-layer resolution From 02d7f88b652b4b1da973720e448649c23205fdc8 Mon Sep 17 00:00:00 2001 From: gabemontero Date: Tue, 21 Jul 2026 15:20:39 -0400 Subject: [PATCH 06/19] fix(boost): RHDHPLAN-1513 address review feedback on openspec docs - Fix augment workspace paths to boost paths in proposal.md Impact - Standardize config keys to boost.connectors.* namespace (BoostConfigKey) - Align method names: resolve(), invalidate() (no args), setOverride() - Align tasks.md REST endpoints with design.md POST /api/boost/admin/config - Document flat-key write semantics (single leaf BoostConfigKey per write) - Clarify startup gate (catalog.providers.*.enabled) vs runtime sync-skip - Align degraded health derivation with design.md (mixed results) - Remove db-only runtime state from configScope table (health store owns it) - Replace in-memory run registry with coreServices.cache/SchedulerService Co-Authored-By: Claude Opus 4.6 Signed-off-by: gabemontero --- .../connector-config-hot-reload/design.md | 16 ++++++------- .../specs/config-admin-ui/spec.md | 24 +++++++++---------- .../specs/hot-reload-propagation/spec.md | 8 +++---- .../connector-config-hot-reload/tasks.md | 8 +++---- .../ingestion-health-dashboard/design.md | 2 +- .../ingestion-health-dashboard/proposal.md | 6 ++--- 6 files changed, 31 insertions(+), 33 deletions(-) diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md index 1eb9039932c..ce914ac690c 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md @@ -16,7 +16,7 @@ Connector config becomes a new scope under this existing infrastructure. ## Goals -- Extend `RuntimeConfigResolver` to connector settings, not create a new config system +- Extend `RuntimeConfigResolver` to connector settings (runtime sync-skip via `boost.connectors.*.enabled`), not create a new config system. Note: startup registration is governed by `catalog.providers..enabled` at module init time (shared-infra Decision 4) — a provider never registered at startup cannot be hot-enabled at runtime. Hot-reload controls sync behavior of already-registered providers. - Hot-reload enable/disable, endpoint URL, and schedule changes within 30s - Preserve deployment-time config (TLS mount paths, K8s Secret references) as YAML-only - Admin UI for connector config with RBAC gating @@ -46,8 +46,9 @@ This reuses proven infrastructure (tested in production for core boost settings) ```typescript // Connector entity provider reads config via RuntimeConfigResolver -const connectorConfig = - await runtimeConfigResolver.getConfig('connectors.jira'); +const connectorConfig = await runtimeConfigResolver.resolve( + 'boost.connectors.jira', +); if (!connectorConfig.enabled) { this.logger.info('Jira connector disabled via runtime config, skipping sync'); return; @@ -77,11 +78,8 @@ Each connector config field is annotated with `configScope` to control which lay | `namespace` | `yaml-only` | Namespace is deployment-time config (can't change active provider's target namespace without restart) | | `batchSize` | `db-overridable` | Admin can tune performance at runtime | | `timeout.connectionMs` | `db-overridable` | Admin can adjust for network conditions at runtime | -| `lastSyncTimestamp` | `db-only` | Runtime state written by provider after sync — no YAML baseline exists | -| `lastSyncOutcome` | `db-only` | Runtime state (success/failure) — written by provider, not configurable | -| `runStatus` | `db-only` | Transient state (running/idle) — no deployment-time equivalent | -**Why db-only exists:** Some fields are pure runtime state — they are written by the system during operation and have no YAML baseline or admin-configurable equivalent. They live exclusively in the database and are never merged with YAML config. The `RuntimeConfigResolver` returns them as-is from the DB layer without two-layer merging. +**Runtime state lives in the health store, not the config resolver:** Fields like `lastSyncTimestamp`, `lastSyncOutcome`, and `runStatus` are pure runtime state owned by the `sync_attempts` table (see ingestion-health-dashboard Decision 1). They are not config — they are operational state written by providers after each sync. Querying them goes through the health API (`GET /api/boost/ingestion-health`), not `RuntimeConfigResolver`. **Why not make everything db-overridable:** Mount paths and Secret references can't change at runtime without a pod restart. Making them `db-overridable` would create false expectations of hot-reload capability. @@ -131,8 +129,8 @@ Admin UI writes connector config changes via `AdminConfigService` — same patte 1. Admin opens connector config section 2. Form fields pre-populated with current merged config (YAML baseline + DB overrides) 3. Admin toggles `enabled` or changes `endpoint` -4. Frontend calls `POST /api/boost/admin/config` with `{ key: "connectors.jira", value: {...} }` -5. Backend validates via Zod schema, writes DB override, invalidates cache +4. Frontend calls `POST /api/boost/admin/config` with flat `BoostConfigKey` — e.g., `{ key: "boost.connectors.jira.enabled", value: false }`. Each write targets a single leaf key; no nested objects, no ambiguity about deep-merge vs replace. +5. Backend validates via Zod schema, writes DB override, calls `RuntimeConfigResolver.invalidate()` (whole-cache invalidation) 6. Frontend shows immediate visual feedback ("Saved — will take effect within 30s + next reconciliation cycle") **YAML-only fields (read-only in UI):** TLS mount paths, Secret references shown as read-only info. Tooltip: "Deployment-time config. Edit YAML to change." diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md index 6a5240035c3..c4919aedfc7 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md @@ -20,16 +20,16 @@ Admin UI provides toggle controls for enabling/disabling connectors. #### Scenario: Toggle connector off - **WHEN** admin clicks toggle to disable Jira connector -- **THEN** frontend calls `POST /api/boost/admin/config` with `{ key: "connectors.jira", value: { enabled: false } }` -- **AND** backend validates via Zod schema, writes DB override, invalidates cache +- **THEN** frontend calls `POST /api/boost/admin/config` with `{ key: "boost.connectors.jira.enabled", value: false }` (flat `BoostConfigKey` — each write targets a single leaf key, no nested objects) +- **AND** backend validates via Zod schema, writes DB override, calls `RuntimeConfigResolver.invalidate()` - **AND** frontend shows immediate visual feedback: "Saved — will take effect within 30 seconds + next reconciliation cycle" - **AND** toggle UI updates to show "Disabled" state #### Scenario: Toggle connector on - **WHEN** admin clicks toggle to enable previously disabled Jira connector -- **THEN** frontend calls `POST /api/boost/admin/config` with `{ key: "connectors.jira", value: { enabled: true } }` -- **AND** backend writes DB override, invalidates cache +- **THEN** frontend calls `POST /api/boost/admin/config` with `{ key: "boost.connectors.jira.enabled", value: true }` +- **AND** backend writes DB override, calls `RuntimeConfigResolver.invalidate()` - **AND** frontend shows immediate visual feedback - **AND** toggle UI updates to show "Enabled" state @@ -48,8 +48,8 @@ Admin UI provides form fields for endpoint URL and sync schedule configuration. - **WHEN** admin edits endpoint URL to `https://jira-staging.example.com` and saves - **THEN** frontend validates URL format before submitting -- **AND** frontend calls `POST /api/boost/admin/config` with `{ key: "connectors.jira", value: { endpoint: "https://jira-staging.example.com" } }` -- **AND** backend validates, writes DB override, invalidates cache +- **AND** frontend calls `POST /api/boost/admin/config` with `{ key: "boost.connectors.jira.endpoint", value: "https://jira-staging.example.com" }` +- **AND** backend validates, writes DB override, calls `RuntimeConfigResolver.invalidate()` - **AND** UI shows: "Saved — new endpoint will be used on next sync (within schedule interval)" #### Scenario: Schedule interval field @@ -61,8 +61,8 @@ Admin UI provides form fields for endpoint URL and sync schedule configuration. #### Scenario: Change schedule interval - **WHEN** admin changes schedule from "5 minutes" to "10 minutes" and saves -- **THEN** frontend calls `POST /api/boost/admin/config` with `{ key: "connectors.jira", value: { schedule: { intervalMs: 600000 } } }` -- **AND** backend validates, writes DB override, invalidates cache +- **THEN** frontend calls `POST /api/boost/admin/config` with `{ key: "boost.connectors.jira.schedule.intervalMs", value: 600000 }` +- **AND** backend validates, writes DB override, calls `RuntimeConfigResolver.invalidate()` - **AND** UI shows: "Saved — new schedule will take effect on next cycle" #### Scenario: Cron schedule field @@ -99,13 +99,13 @@ Admin UI saves connector config changes via `AdminConfigService` with schema val - **THEN** frontend calls `POST /api/boost/admin/config` with payload: ```json { - "key": "connectors.jira", - "value": { "endpoint": "https://jira-staging.example.com" } + "key": "boost.connectors.jira.endpoint", + "value": "https://jira-staging.example.com" } ``` -- **AND** backend `AdminConfigService.setConfig()` validates via Jira connector Zod schema +- **AND** backend `AdminConfigService.setOverride()` validates via Jira connector Zod schema - **AND** backend writes DB override to `admin_config` table -- **AND** backend calls `RuntimeConfigResolver.invalidate('connectors.jira')` +- **AND** backend calls `RuntimeConfigResolver.invalidate()` (whole-cache invalidation, no key parameter) - **AND** backend returns success response - **AND** frontend displays success notification diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/hot-reload-propagation/spec.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/hot-reload-propagation/spec.md index 76b1a3470d8..26c308cb6ef 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/hot-reload-propagation/spec.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/hot-reload-propagation/spec.md @@ -102,15 +102,15 @@ Database override writes trigger immediate cache invalidation. #### Scenario: Cache invalidated on DB write - **WHEN** admin writes DB override via `AdminConfigService` -- **THEN** `AdminConfigService` calls `RuntimeConfigResolver.invalidate('connectors.jira')` -- **AND** cache entry for `connectors.jira` is deleted -- **AND** next `RuntimeConfigResolver.getConfig('connectors.jira')` call fetches fresh YAML + DB overrides +- **THEN** `AdminConfigService` calls `RuntimeConfigResolver.invalidate()` (whole-cache invalidation, no key parameter) +- **AND** all cached config entries are cleared +- **AND** next `RuntimeConfigResolver.resolve('boost.connectors.jira')` call fetches fresh YAML + DB overrides #### Scenario: TTL-based cache refresh - **WHEN** no DB override write occurs within 30s - **THEN** `RuntimeConfigResolver` cache entry expires after 30s TTL -- **AND** next `getConfig` call fetches fresh YAML + DB overrides +- **AND** next `resolve` call fetches fresh YAML + DB overrides - **AND** ensures eventual consistency even if invalidation signal is missed ### Requirement: Provider Behavior During Config Transition diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md index 8ac4b1daaef..9ed056f0031 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md @@ -16,7 +16,7 @@ ## 2. RuntimeConfigResolver Extension (P0) — RHIDP-15340 - [ ] 2.1 Extend `RuntimeConfigResolver` to support connector config scope (e.g., `connectors.jira`, `connectors.github`) -- [ ] 2.2 Implement `getConfig(key: string)` method for connector config keys +- [ ] 2.2 Extend `resolve(key: BoostConfigKey)` method to support connector config keys (e.g., `boost.connectors.jira`) - [ ] 2.3 Implement two-layer merge: YAML baseline from `ConfigApi` + DB overrides from `AdminConfigService` - [ ] 2.4 Implement cache with 30s TTL for merged connector config - [ ] 2.5 Implement immediate cache invalidation on DB override write @@ -28,7 +28,7 @@ ## 3. Hot-Reload Propagation (P0) — RHIDP-15341 -- [ ] 3.1 Update Jira entity provider to read config via `RuntimeConfigResolver.getConfig('connectors.jira')` at reconciliation cycle start +- [ ] 3.1 Update Jira entity provider to read config via `RuntimeConfigResolver.resolve('boost.connectors.jira')` at reconciliation cycle start - [ ] 3.2 Implement enable/disable check: skip sync if `enabled: false` - [ ] 3.3 Implement endpoint URL propagation: use DB override endpoint if present, else YAML baseline - [ ] 3.4 Implement schedule change propagation: reschedule task with new `schedule.intervalMs` or `schedule.cron` from merged config @@ -65,8 +65,8 @@ ## 6. AdminConfigService Integration (P1) -- [ ] 6.1 Add connector config endpoints to `AdminConfigService` backend API: `GET /api/boost/admin/config/connectors/:connectorId`, `POST /api/boost/admin/config/connectors/:connectorId` -- [ ] 6.2 Implement Zod schema validation in `setConfig()` method before DB write +- [ ] 6.1 Extend existing `POST /api/boost/admin/config` endpoint to accept connector config keys (e.g., `{ key: "boost.connectors.jira.enabled", value: false }`). Add `GET /api/boost/admin/config?key=boost.connectors.:connectorId` for reading merged connector config. +- [ ] 6.2 Implement Zod schema validation in `setOverride()` method before DB write - [ ] 6.3 Implement `configScope` enforcement: reject writes for `yaml-only` fields - [ ] 6.4 Implement cache invalidation call to `RuntimeConfigResolver.invalidate()` after DB write - [ ] 6.5 Add audit logging for connector config changes (timestamp, user, changed fields, old/new values) diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md index 3885294855e..76b986a42d9 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md @@ -69,7 +69,7 @@ Timeout is configurable (default 10 minutes) via `boost.ingestion.forceSyncTimeo **Why:** Force Sync is a manual override for cases where scheduled sync failed or admin needs immediate refresh (e.g., after fixing auth credentials). Timeout prevents hung requests. Concurrency prevention avoids duplicate work and state corruption. -**How to apply:** API handler checks connector run state (via in-memory run registry or `SchedulerService.getTaskStatus()`), returns 409 if running. Otherwise calls `SchedulerService.triggerTask()` or `provider.run()` with timeout wrapper. Response includes `runId` for polling status. UI polls `GET /api/boost/ingestion-health/:connectorId/force-sync/:runId` for completion. +**How to apply:** API handler checks connector run state via `SchedulerService.getTaskStatus()` (preferred — multi-replica safe) or `coreServices.cache` (if scheduler integration is unavailable). No raw `Map<>` in-memory registries — they break in multi-replica deployments. Returns 409 if running. Otherwise calls `SchedulerService.triggerTask()` or `provider.run()` with timeout wrapper. Response includes `runId` for polling status. UI polls `GET /api/boost/ingestion-health/:connectorId/force-sync/:runId` for completion. ### Decision 3: Admin UI in existing boost admin panel — new route/tab diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/proposal.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/proposal.md index fa9b5f67276..6274b81043c 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/proposal.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/proposal.md @@ -18,7 +18,7 @@ Air-gapped deployments make connectivity failures common and hard to diagnose. A - REST API exposing per-connector health: enabled state, last sync attempt/success timestamps, health status (healthy/degraded/failing), most recent error - Data model tracks sync attempts with timestamps and outcomes (success/failure, assets added/updated/removed counts, error type, error message) -- Health status derivation: healthy = last 3 sync attempts succeeded, degraded = last sync succeeded but recent failures, failing = last 3 sync attempts failed +- Health status derivation: healthy = last 3 sync attempts succeeded, degraded = mixed results (not all-success and not all-failure), failing = last 3 sync attempts failed - RBAC-gated via boost admin permissions - Force Sync API endpoint triggers provider's `run()` method outside scheduled cadence with configurable timeout @@ -56,6 +56,6 @@ Air-gapped deployments make connectivity failures common and hard to diagnose. A - `plugins/boost-common/src/types/ingestion-health.ts` — health status types - `plugins/boost-backend/src/ingestion/health-tracker.ts` — health status derivation logic - `plugins/boost-backend/src/ingestion/error-classifier.ts` — error classification utilities -- `workspaces/augment/plugins/augment/src/components/AdminPanels/IngestionHealthPanel.tsx` — admin UI component -- `workspaces/augment/plugins/augment/src/components/AdminPanels/AdminLayout.tsx` — navigation integration +- `plugins/boost/src/components/AdminPanels/IngestionHealthPanel.tsx` — admin UI component +- `plugins/boost/src/components/AdminPanels/AdminLayout.tsx` — navigation integration - Connector providers (`github-connector`, `gitlab-connector`, `jira-connector`) — integrate health tracking hooks From 0e6e6469358b81f261eb302e6dfb9faf09180995 Mon Sep 17 00:00:00 2001 From: gabemontero Date: Tue, 21 Jul 2026 18:08:17 -0400 Subject: [PATCH 07/19] fix(boost): RHDHPLAN-1513 address fullsend review findings on PR #3755 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix 1 (medium): getConfig() → resolve(), connectors.jira → boost.connectors.jira in config-schemas/spec.md — missed in Marek fix Fix 2: Remove stale db-only configScope from proposal.md and tasks.md — runtime state lives in health store, not config resolver (per Marek) Fix 3: Add tls.caFile to task 1.1 Jira Zod schema field list — matches design.md configScope table Fix 4: Replace :connectorId Express-style placeholder with in task 6.1 query param Fix 5: Add CHECK(score >= 0.0 AND score <= 1.0) constraint to boost_quality_scores DECIMAL column in ingestion-audit-metrics design Fix 6: Remove unconventional Spec: title prefix from 5 spec files Fix 7: Standardize status header format in 2 upstream-schema-alignment specs — bold+emoji → blockquote format matching other specs Co-Authored-By: Claude Opus 4.6 Signed-off-by: gabemontero --- .../changes/connector-config-hot-reload/proposal.md | 2 +- .../specs/config-schemas/spec.md | 8 ++++---- .../openspec/changes/connector-config-hot-reload/tasks.md | 6 +++--- .../openspec/changes/ingestion-audit-metrics/design.md | 2 +- .../ingestion-audit-metrics/specs/analytics-api/spec.md | 2 +- .../ingestion-audit-metrics/specs/audit-events/spec.md | 2 +- .../specs/eval-hub-integration/spec.md | 2 +- .../specs/annotation-specification/spec.md | 8 ++++---- .../specs/migration-readiness-tooling/spec.md | 8 ++++---- 9 files changed, 20 insertions(+), 20 deletions(-) diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md index bbe6c005bdf..a53a585095f 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md @@ -15,7 +15,7 @@ The key distinction: Backstage's built-in `ConfigApi` loads config at startup wi ### Config Schemas - Zod schema definitions for per-connector settings: `enabled`, `endpoint`, `schedule`, `tls`, `credentials` -- `configScope` annotation on each field: `yaml-only` (mount paths, K8s Secret references), `db-overridable` (enable/disable, endpoint URL, schedule), `db-only` (runtime-only state — last sync timestamp, run status) +- `configScope` annotation on each field: `yaml-only` (mount paths, K8s Secret references), `db-overridable` (enable/disable, endpoint URL, schedule). Runtime operational state (last sync timestamp, run status) lives in the health store (`sync_attempts` table), not the config resolver. - Schema validation rejects invalid connector config values before write - Integration with `RuntimeConfigResolver`'s two-layer resolution diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md index 0fa53b764f0..fe0d4375cab 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md @@ -34,16 +34,16 @@ Each connector has a Zod schema defining all configuration fields with `configSc #### Scenario: Two-layer resolution with schema validation -- **WHEN** `RuntimeConfigResolver.getConfig('connectors.jira')` is called +- **WHEN** `RuntimeConfigResolver.resolve('boost.connectors.jira')` is called - **THEN** resolver reads YAML baseline from `ConfigApi` at `boost.connectors.jira` -- **AND** resolver reads DB overrides from `AdminConfigService` for key `connectors.jira` +- **AND** resolver reads DB overrides from `AdminConfigService` for key `boost.connectors.jira` - **AND** resolver merges YAML + DB, validating against Jira connector Zod schema - **AND** resolver returns merged config object with 30s TTL cache #### Scenario: DB override takes precedence over YAML - **WHEN** YAML config has `enabled: true` and DB override has `enabled: false` -- **THEN** `RuntimeConfigResolver.getConfig('connectors.jira')` returns `enabled: false` +- **THEN** `RuntimeConfigResolver.resolve('boost.connectors.jira')` returns `enabled: false` #### Scenario: YAML-only field rejects DB override @@ -79,7 +79,7 @@ Connector config schemas support versioning for backward compatibility. #### Scenario: Schema version stored with DB override -- **WHEN** admin writes DB override for `connectors.jira` +- **WHEN** admin writes DB override for `boost.connectors.jira` - **THEN** DB entry includes schema version (e.g., `schemaVersion: 1`) - **AND** future reads validate against matching schema version diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md index 9ed056f0031..5b1a7e20f55 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md @@ -2,7 +2,7 @@ ## 1. Zod Schema Definitions (P0) — RHIDP-15340 -- [ ] 1.1 Define Jira connector config Zod schema with fields: `enabled` (boolean), `endpoint` (URL), `schedule.intervalMs` (number), `schedule.cron` (string), `credentials.secretRef` (string), `credentials.secretKey` (string), `namespace` (string), `batchSize` (number), `timeout.connectionMs` (number) +- [ ] 1.1 Define Jira connector config Zod schema with fields: `enabled` (boolean), `endpoint` (URL), `schedule.intervalMs` (number), `schedule.cron` (string), `tls.caFile` (string), `credentials.secretRef` (string), `credentials.secretKey` (string), `namespace` (string), `batchSize` (number), `timeout.connectionMs` (number) - [ ] 1.2 Annotate each Jira config field with `configScope`: `enabled`, `endpoint`, `schedule.*`, `batchSize`, `timeout.*` → `db-overridable`; `credentials.*`, `namespace` → `yaml-only` - [ ] 1.3 Define GitHub connector config Zod schema with same field pattern - [ ] 1.4 Define GitLab connector config Zod schema with same field pattern @@ -65,7 +65,7 @@ ## 6. AdminConfigService Integration (P1) -- [ ] 6.1 Extend existing `POST /api/boost/admin/config` endpoint to accept connector config keys (e.g., `{ key: "boost.connectors.jira.enabled", value: false }`). Add `GET /api/boost/admin/config?key=boost.connectors.:connectorId` for reading merged connector config. +- [ ] 6.1 Extend existing `POST /api/boost/admin/config` endpoint to accept connector config keys (e.g., `{ key: "boost.connectors.jira.enabled", value: false }`). Add `GET /api/boost/admin/config?key=boost.connectors.` for reading merged connector config. - [ ] 6.2 Implement Zod schema validation in `setOverride()` method before DB write - [ ] 6.3 Implement `configScope` enforcement: reject writes for `yaml-only` fields - [ ] 6.4 Implement cache invalidation call to `RuntimeConfigResolver.invalidate()` after DB write @@ -85,7 +85,7 @@ ## 8. Documentation (P2) - [ ] 8.1 Document `RuntimeConfigResolver` extension for connector config in architecture docs -- [ ] 8.2 Document `configScope` annotations and their meaning (`yaml-only`, `db-overridable`, `db-only`) +- [ ] 8.2 Document `configScope` annotations and their meaning (`yaml-only`, `db-overridable`). Note: runtime operational state lives in the health store (`sync_attempts` table), not the config resolver. - [ ] 8.3 Document connector config admin UI usage (how to toggle, change endpoint/schedule) - [ ] 8.4 Document propagation latency: 30s TTL + reconciliation interval - [ ] 8.5 Document credential rotation workflow and latency (≤60s kubelet + reconciliation interval) diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md index e5c2e9f07b7..4672fa6d888 100644 --- a/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md @@ -127,7 +127,7 @@ CREATE TABLE boost_quality_scores ( id SERIAL PRIMARY KEY, skill_entity_ref VARCHAR(255) NOT NULL, eval_source VARCHAR(100) NOT NULL, -- 'lighteval', 'ibm-clear', 'guidellm', etc. - score DECIMAL(5, 3) NOT NULL, -- 0.000 to 1.000 (normalized) + score DECIMAL(5, 3) NOT NULL CHECK (score >= 0.0 AND score <= 1.0), -- 0.000 to 1.000 (normalized) timestamp TIMESTAMP NOT NULL DEFAULT NOW(), metadata JSONB -- extensible for eval-framework-specific data ); diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/analytics-api/spec.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/analytics-api/spec.md index 1448b30aae9..0974338db08 100644 --- a/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/analytics-api/spec.md +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/analytics-api/spec.md @@ -1,4 +1,4 @@ -# Spec: Analytics Metrics REST API +# Analytics Metrics REST API > **Status:** 🔀 CONSOLIDATED into RHIDP-15277 (RHDHPLAN-1508) > **Story:** RHIDP-15344 (Closed — absorbed by RHIDP-15277 epic scope) diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/audit-events/spec.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/audit-events/spec.md index 0f84db34290..622fe8c4699 100644 --- a/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/audit-events/spec.md +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/audit-events/spec.md @@ -1,4 +1,4 @@ -# Spec: Ingestion Sync Audit Events +# Ingestion Sync Audit Events > **Status:** 🔀 CONSOLIDATED into RHIDP-15280 (RHDHPLAN-1508) > **Story:** RHIDP-15343 (Closed — absorbed by RHIDP-15280) diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/eval-hub-integration/spec.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/eval-hub-integration/spec.md index 0c0ae6efde9..32b53905435 100644 --- a/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/eval-hub-integration/spec.md +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/eval-hub-integration/spec.md @@ -1,4 +1,4 @@ -# Spec: Eval Hub Integration for Skill Quality Scores +# Eval Hub Integration for Skill Quality Scores > **Status:** 🔀 CONSOLIDATED into RHIDP-15277 (RHDHPLAN-1508) > **Story:** RHIDP-15345 (Closed — absorbed by RHIDP-15277 epic scope) diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md index b07ad900e8d..25bd501d4ab 100644 --- a/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md @@ -1,8 +1,8 @@ -# Spec: Annotation Specification Document +# Annotation Specification Document -**Epic:** RHIDP-15334 — Upstream Schema Alignment Readiness -**Story:** RHIDP-15346 — Annotation specification document -**Status:** 🟡 Draft +> **Status: Draft** — Pre-implementation specification. Subject to change during implementation. +> +> **Epic:** RHIDP-15334 (Upstream Schema Alignment Readiness). **Story:** RHIDP-15346 (Annotation specification document). ## Overview diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/migration-readiness-tooling/spec.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/migration-readiness-tooling/spec.md index 31b23f4e5aa..969c46c4ddc 100644 --- a/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/migration-readiness-tooling/spec.md +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/migration-readiness-tooling/spec.md @@ -1,8 +1,8 @@ -# Spec: Migration-Readiness Tooling Scaffold +# Migration-Readiness Tooling Scaffold -**Epic:** RHIDP-15334 — Upstream Schema Alignment Readiness -**Story:** RHIDP-15347 — Dry-run migration-readiness tooling scaffold -**Status:** 🟡 Draft +> **Status: Draft** — Pre-implementation specification. Subject to change during implementation. +> +> **Epic:** RHIDP-15334 (Upstream Schema Alignment Readiness). **Story:** RHIDP-15347 (Dry-run migration-readiness tooling scaffold). ## Overview From b56a5455983e3b908cf0e9b0aa6a2ec77fadfb3a Mon Sep 17 00:00:00 2001 From: gabemontero Date: Wed, 22 Jul 2026 13:28:56 -0400 Subject: [PATCH 08/19] fix(boost): RHDHPLAN-1513 address Marek review feedback on PR #3755 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thread 1: augment → boost workspace references in ingestion-health-dashboard Thread 2: resolve() leaf key alignment — use boost.connectors.jira.enabled not boost.connectors.jira (namespace) Thread 3: drop 'in-memory or' from task 6.2 — SchedulerService only Thread 4: sync_attempts → boost_sync_attempts table naming convention Thread 6: add config namespace mapping table, remove tls.caFile from boost.connectors scope (belongs under catalog.providers) Thread 7: add unknown status to health status enum in proposal and spec Co-Authored-By: Claude Opus 4.6 Signed-off-by: gabemontero --- .../connector-config-hot-reload/design.md | 23 ++++++++++++------- .../connector-config-hot-reload/proposal.md | 2 +- .../specs/config-schemas/spec.md | 12 +++++----- .../specs/hot-reload-propagation/spec.md | 2 +- .../connector-config-hot-reload/tasks.md | 6 ++--- .../ingestion-health-dashboard/design.md | 10 ++++---- .../ingestion-health-dashboard/proposal.md | 6 ++--- .../specs/health-status-api/spec.md | 6 ++--- .../ingestion-health-dashboard/tasks.md | 8 +++---- 9 files changed, 41 insertions(+), 34 deletions(-) diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md index ce914ac690c..5be5ec709ec 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md @@ -17,6 +17,14 @@ Connector config becomes a new scope under this existing infrastructure. ## Goals - Extend `RuntimeConfigResolver` to connector settings (runtime sync-skip via `boost.connectors.*.enabled`), not create a new config system. Note: startup registration is governed by `catalog.providers..enabled` at module init time (shared-infra Decision 4) — a provider never registered at startup cannot be hot-enabled at runtime. Hot-reload controls sync behavior of already-registered providers. + +**Config namespace ownership:** + +| Namespace | Scope | Fields | Layer | +| -------------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ---------------- | +| `catalog.providers..*` | Shared infrastructure (startup, TLS, credentials) | `enabled` (startup registration), `tls.caFile`, `credentials.secretRef`, `credentials.secretKey`, `namespace` | YAML-only | +| `boost.connectors..*` | Runtime behavior (hot-reloadable) | `enabled` (sync-skip), `endpoint`, `schedule.intervalMs`, `schedule.cron`, `batchSize`, `timeout.connectionMs` | `db-overridable` | + - Hot-reload enable/disable, endpoint URL, and schedule changes within 30s - Preserve deployment-time config (TLS mount paths, K8s Secret references) as YAML-only - Admin UI for connector config with RBAC gating @@ -45,18 +53,18 @@ This reuses proven infrastructure (tested in production for core boost settings) **Implementation pattern:** ```typescript -// Connector entity provider reads config via RuntimeConfigResolver -const connectorConfig = await runtimeConfigResolver.resolve( - 'boost.connectors.jira', +// Connector entity provider reads leaf keys via RuntimeConfigResolver +const enabled = await runtimeConfigResolver.resolve( + 'boost.connectors.jira.enabled', ); -if (!connectorConfig.enabled) { +if (!enabled) { this.logger.info('Jira connector disabled via runtime config, skipping sync'); return; } const endpoint = - connectorConfig.endpoint || - this.config.getString('boost.connectors.jira.endpoint'); + (await runtimeConfigResolver.resolve('boost.connectors.jira.endpoint')) || + this.config.getString('catalog.providers.jira.endpoint'); await this.syncClient.connect(endpoint); ``` @@ -71,7 +79,6 @@ Each connector config field is annotated with `configScope` to control which lay | `enabled` | `db-overridable` | Admin can toggle without YAML change | | `endpoint` | `db-overridable` | Admin can switch environments without deployment | | `schedule.intervalMs` | `db-overridable` | Admin can tune sync frequency at runtime | -| `tls.caFile` | `yaml-only` | Mount path can't change at runtime (requires pod restart) | | `credentials.secretRef` | `yaml-only` | K8s Secret references are deployment-time config | | `credentials.secretKey` | `yaml-only` | K8s Secret key names are deployment-time config | | `schedule.cron` | `db-overridable` | Admin can change cron schedule at runtime | @@ -79,7 +86,7 @@ Each connector config field is annotated with `configScope` to control which lay | `batchSize` | `db-overridable` | Admin can tune performance at runtime | | `timeout.connectionMs` | `db-overridable` | Admin can adjust for network conditions at runtime | -**Runtime state lives in the health store, not the config resolver:** Fields like `lastSyncTimestamp`, `lastSyncOutcome`, and `runStatus` are pure runtime state owned by the `sync_attempts` table (see ingestion-health-dashboard Decision 1). They are not config — they are operational state written by providers after each sync. Querying them goes through the health API (`GET /api/boost/ingestion-health`), not `RuntimeConfigResolver`. +**Runtime state lives in the health store, not the config resolver:** Fields like `lastSyncTimestamp`, `lastSyncOutcome`, and `runStatus` are pure runtime state owned by the `boost_sync_attempts` table (see ingestion-health-dashboard Decision 1). They are not config — they are operational state written by providers after each sync. Querying them goes through the health API (`GET /api/boost/ingestion-health`), not `RuntimeConfigResolver`. **Why not make everything db-overridable:** Mount paths and Secret references can't change at runtime without a pod restart. Making them `db-overridable` would create false expectations of hot-reload capability. diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md index a53a585095f..2f01468387d 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md @@ -15,7 +15,7 @@ The key distinction: Backstage's built-in `ConfigApi` loads config at startup wi ### Config Schemas - Zod schema definitions for per-connector settings: `enabled`, `endpoint`, `schedule`, `tls`, `credentials` -- `configScope` annotation on each field: `yaml-only` (mount paths, K8s Secret references), `db-overridable` (enable/disable, endpoint URL, schedule). Runtime operational state (last sync timestamp, run status) lives in the health store (`sync_attempts` table), not the config resolver. +- `configScope` annotation on each field: `yaml-only` (mount paths, K8s Secret references), `db-overridable` (enable/disable, endpoint URL, schedule). Runtime operational state (last sync timestamp, run status) lives in the health store (`boost_sync_attempts` table), not the config resolver. - Schema validation rejects invalid connector config values before write - Integration with `RuntimeConfigResolver`'s two-layer resolution diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md index fe0d4375cab..a3e6e0bf22d 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md @@ -34,16 +34,16 @@ Each connector has a Zod schema defining all configuration fields with `configSc #### Scenario: Two-layer resolution with schema validation -- **WHEN** `RuntimeConfigResolver.resolve('boost.connectors.jira')` is called -- **THEN** resolver reads YAML baseline from `ConfigApi` at `boost.connectors.jira` -- **AND** resolver reads DB overrides from `AdminConfigService` for key `boost.connectors.jira` -- **AND** resolver merges YAML + DB, validating against Jira connector Zod schema -- **AND** resolver returns merged config object with 30s TTL cache +- **WHEN** `RuntimeConfigResolver.resolve('boost.connectors.jira.enabled')` is called +- **THEN** resolver reads YAML baseline value from `ConfigApi` at key path `boost.connectors.jira.enabled` +- **AND** resolver reads any DB override from `AdminConfigService` for leaf key `boost.connectors.jira.enabled` +- **AND** resolver returns the DB override value if present, otherwise the YAML baseline value, validated against the Jira connector Zod schema +- **AND** resolved value is cached with 30s TTL #### Scenario: DB override takes precedence over YAML - **WHEN** YAML config has `enabled: true` and DB override has `enabled: false` -- **THEN** `RuntimeConfigResolver.resolve('boost.connectors.jira')` returns `enabled: false` +- **THEN** `RuntimeConfigResolver.resolve('boost.connectors.jira.enabled')` returns `false` #### Scenario: YAML-only field rejects DB override diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/hot-reload-propagation/spec.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/hot-reload-propagation/spec.md index 26c308cb6ef..e78567c1a22 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/hot-reload-propagation/spec.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/hot-reload-propagation/spec.md @@ -104,7 +104,7 @@ Database override writes trigger immediate cache invalidation. - **WHEN** admin writes DB override via `AdminConfigService` - **THEN** `AdminConfigService` calls `RuntimeConfigResolver.invalidate()` (whole-cache invalidation, no key parameter) - **AND** all cached config entries are cleared -- **AND** next `RuntimeConfigResolver.resolve('boost.connectors.jira')` call fetches fresh YAML + DB overrides +- **AND** next `RuntimeConfigResolver.resolve('boost.connectors.jira.enabled')` call fetches fresh YAML + DB override value #### Scenario: TTL-based cache refresh diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md index 5b1a7e20f55..b51f69648bd 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md @@ -16,7 +16,7 @@ ## 2. RuntimeConfigResolver Extension (P0) — RHIDP-15340 - [ ] 2.1 Extend `RuntimeConfigResolver` to support connector config scope (e.g., `connectors.jira`, `connectors.github`) -- [ ] 2.2 Extend `resolve(key: BoostConfigKey)` method to support connector config keys (e.g., `boost.connectors.jira`) +- [ ] 2.2 Extend `resolve(key: BoostConfigKey)` method to support connector leaf config keys (e.g., `boost.connectors.jira.enabled`) - [ ] 2.3 Implement two-layer merge: YAML baseline from `ConfigApi` + DB overrides from `AdminConfigService` - [ ] 2.4 Implement cache with 30s TTL for merged connector config - [ ] 2.5 Implement immediate cache invalidation on DB override write @@ -28,7 +28,7 @@ ## 3. Hot-Reload Propagation (P0) — RHIDP-15341 -- [ ] 3.1 Update Jira entity provider to read config via `RuntimeConfigResolver.resolve('boost.connectors.jira')` at reconciliation cycle start +- [ ] 3.1 Update Jira entity provider to read config via `RuntimeConfigResolver.resolve('boost.connectors.jira.enabled')` (and other leaf keys) at reconciliation cycle start - [ ] 3.2 Implement enable/disable check: skip sync if `enabled: false` - [ ] 3.3 Implement endpoint URL propagation: use DB override endpoint if present, else YAML baseline - [ ] 3.4 Implement schedule change propagation: reschedule task with new `schedule.intervalMs` or `schedule.cron` from merged config @@ -85,7 +85,7 @@ ## 8. Documentation (P2) - [ ] 8.1 Document `RuntimeConfigResolver` extension for connector config in architecture docs -- [ ] 8.2 Document `configScope` annotations and their meaning (`yaml-only`, `db-overridable`). Note: runtime operational state lives in the health store (`sync_attempts` table), not the config resolver. +- [ ] 8.2 Document `configScope` annotations and their meaning (`yaml-only`, `db-overridable`). Note: runtime operational state lives in the health store (`boost_sync_attempts` table), not the config resolver. - [ ] 8.3 Document connector config admin UI usage (how to toggle, change endpoint/schedule) - [ ] 8.4 Document propagation latency: 30s TTL + reconciliation interval - [ ] 8.5 Document credential rotation workflow and latency (≤60s kubelet + reconciliation interval) diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md index 76b986a42d9..e58624a6f8f 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md @@ -4,7 +4,7 @@ > **RHDHPLAN-1513 Consolidation (2026-07-08):** Epic RHIDP-15333 (Ingestion Audit Logging & Metrics) was closed and consolidated into RHIDP-15277 (RHDHPLAN-1508). This health dashboard is unaffected — audit event patterns referenced in this design continue to come from RHIDP-15277. The Neo4j sync panel (RHIDP-15338) depends on RHIDP-15295 (RHDHPLAN-1507 Neo4j Knowledge Graph Sync Adapter) being implemented first. -Boost implements the ingestion health dashboard as a standard admin panel section following the existing patterns established in the augment workspace's admin panel (model connection, system prompt, agent config sections). The augment reference prototype has no equivalent feature — this is net-new functionality for Boost. The design follows RHDH Extensions Catalog patterns for admin tooling: backend exposes REST API, frontend renders via PatternFly components, RBAC enforced at API layer. +Boost implements the ingestion health dashboard as a standard admin panel section following the existing patterns established in the boost workspace's admin panel (model connection, system prompt, agent config sections). The augment reference prototype has no equivalent feature — this is net-new functionality for Boost. The design follows RHDH Extensions Catalog patterns for admin tooling: backend exposes REST API, frontend renders via PatternFly components, RBAC enforced at API layer. ## Goals @@ -13,7 +13,7 @@ Boost implements the ingestion health dashboard as a standard admin panel sectio - Distinguish intentionally disabled connectors from unexpectedly failing ones in air-gapped clusters - Force Sync capability for manual intervention - Neo4j graph sync operational monitoring as distinct section -- Integrate seamlessly into existing augment admin panel navigation +- Integrate seamlessly into existing boost admin panel navigation ## Non-Goals @@ -30,7 +30,7 @@ Boost implements the ingestion health dashboard as a standard admin panel sectio The health data model tracks sync attempts in a dedicated database table with schema: ```sql -CREATE TABLE sync_attempts ( +CREATE TABLE boost_sync_attempts ( id SERIAL PRIMARY KEY, connector_id TEXT NOT NULL, timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), @@ -43,7 +43,7 @@ CREATE TABLE sync_attempts ( duration_ms INTEGER NOT NULL ); -CREATE INDEX idx_sync_attempts_connector_ts ON sync_attempts (connector_id, timestamp DESC); +CREATE INDEX idx_boost_sync_attempts_connector_ts ON boost_sync_attempts (connector_id, timestamp DESC); ``` Health status is derived from the last N sync attempts (default N=3): @@ -73,7 +73,7 @@ Timeout is configurable (default 10 minutes) via `boost.ingestion.forceSyncTimeo ### Decision 3: Admin UI in existing boost admin panel — new route/tab -The ingestion health UI is a new section in the augment workspace's existing admin panel at route `/admin/ingestion-health`. Follows the pattern established by `/admin/model-connection`, `/admin/system-prompt`, `/admin/agent-config`. +The ingestion health UI is a new section in the boost workspace's existing admin panel at route `/admin/ingestion-health`. Follows the pattern established by `/admin/model-connection`, `/admin/system-prompt`, `/admin/agent-config`. Navigation: Adds "Ingestion Health" item to admin panel sidebar (`AdminLayout.tsx`). Content: `IngestionHealthPanel.tsx` renders per-connector health cards using PatternFly `Card`, `CardHeader`, `CardBody` with status badges (`Label` component with green/yellow/red variants). diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/proposal.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/proposal.md index 6274b81043c..b3a1b10dab5 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/proposal.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/proposal.md @@ -16,7 +16,7 @@ Air-gapped deployments make connectivity failures common and hard to diagnose. A ### Health Status API -- REST API exposing per-connector health: enabled state, last sync attempt/success timestamps, health status (healthy/degraded/failing), most recent error +- REST API exposing per-connector health: enabled state, last sync attempt/success timestamps, health status (healthy/degraded/failing/unknown), most recent error - Data model tracks sync attempts with timestamps and outcomes (success/failure, assets added/updated/removed counts, error type, error message) - Health status derivation: healthy = last 3 sync attempts succeeded, degraded = mixed results (not all-success and not all-failure), failing = last 3 sync attempts failed - RBAC-gated via boost admin permissions @@ -27,7 +27,7 @@ Air-gapped deployments make connectivity failures common and hard to diagnose. A - Admin dashboard section showing per-connector health cards with status indicators (PatternFly green/yellow/red), timestamps, error summaries, "Force Sync" buttons - Built on PatternFly design system following existing admin panel patterns (model connection, system prompt, agent config sections) - Loading states, error states, empty states for zero connectors -- Integrates into augment workspace's existing admin panel navigation +- Integrates into boost workspace's existing admin panel navigation ### Error Classification @@ -44,7 +44,7 @@ Air-gapped deployments make connectivity failures common and hard to diagnose. A ### Disconnected-Cluster Health View Differentiation -- Three-state model: enabled+healthy, enabled+failing, disabled +- Four-state model: enabled+healthy, enabled+failing, disabled, unknown (never synced) - Disabled connectors shown with grey/muted treatment (not error state) - Enabled+failing connectors shown with red/alert indicators - No "disabled" state generates alerts or error indicators — prevents false alarms in air-gapped environments diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/health-status-api/spec.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/health-status-api/spec.md index 7d0f9707c80..4ceba83845d 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/health-status-api/spec.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/health-status-api/spec.md @@ -14,7 +14,7 @@ The API exposes connector health state for admin dashboard consumption. - **WHEN** `GET /api/boost/ingestion-health` is called with valid boost admin credentials - **THEN** the response is a JSON array of connector health objects -- **AND** each object contains: `connectorId`, `connectorType`, `enabled`, `status` (healthy/degraded/failing/disabled), `lastSyncAttempt` (ISO timestamp), `lastSuccessfulSync` (ISO timestamp or null), `errorSummary` (object or null), `metrics` (object with assetsAdded/Updated/Removed counts) +- **AND** each object contains: `connectorId`, `connectorType`, `enabled`, `status` (healthy/degraded/failing/disabled/unknown), `lastSyncAttempt` (ISO timestamp), `lastSuccessfulSync` (ISO timestamp or null), `errorSummary` (object or null), `metrics` (object with assetsAdded/Updated/Removed counts) - **AND** disabled connectors are excluded from the response unless `?includeDisabled=true` query parameter is set #### Scenario: Health status derivation logic @@ -40,7 +40,7 @@ Each connector sync attempt is recorded in the database. #### Scenario: Sync attempt recorded in database - **WHEN** a connector completes a sync attempt (success or failure) -- **THEN** a record is inserted into the `sync_attempts` table with: `connector_id`, `timestamp`, `outcome` (success/failure), `error_type`, `error_message`, `assets_added`, `assets_updated`, `assets_removed`, `duration_ms` +- **THEN** a record is inserted into the `boost_sync_attempts` table with: `connector_id`, `timestamp`, `outcome` (success/failure), `error_type`, `error_message`, `assets_added`, `assets_updated`, `assets_removed`, `duration_ms` - **AND** the record is queryable via health API within 1 second #### Scenario: Retention policy enforced @@ -80,7 +80,7 @@ Admins can manually trigger connector sync outside scheduled cadence. - **WHEN** `POST /api/boost/ingestion-health/:connectorId/force-sync` is called with valid boost admin credentials - **THEN** the connector provider's `run()` method is invoked immediately - **AND** the response includes a `runId` for polling status -- **AND** the sync attempt is recorded in the `sync_attempts` table upon completion +- **AND** the sync attempt is recorded in the `boost_sync_attempts` table upon completion #### Scenario: Force Sync timeout handling diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/tasks.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/tasks.md index 929cbb4fd91..e21ec5250be 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/tasks.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/tasks.md @@ -2,8 +2,8 @@ ## 1. Health Data Model and Storage (P0) — RHIDP-15335 -- [ ] 1.1 Define `sync_attempts` table schema in database migration (connector_id, timestamp, outcome, error_type, error_message, assets_added/updated/removed, duration_ms) -- [ ] 1.2 Create database migration file for `sync_attempts` table with indexes on (connector_id, timestamp DESC) +- [ ] 1.1 Define `boost_sync_attempts` table schema in database migration (connector_id, timestamp, outcome, error_type, error_message, assets_added/updated/removed, duration_ms) +- [ ] 1.2 Create database migration file for `boost_sync_attempts` table with indexes on (connector_id, timestamp DESC) - [ ] 1.3 Implement `SyncAttemptsRepository` class with methods: `insertSyncAttempt()`, `getLatestAttempts(connectorId, limit)`, `cleanupOldAttempts(connectorId, retentionLimit)` - [ ] 1.4 Add retention policy config schema to `boost.ingestion.healthRetention.maxAttemptsPerConnector` (default 100) - [ ] 1.5 Implement scheduled cleanup job for sync attempts (runs daily, enforces retention policy per connector) @@ -35,7 +35,7 @@ ## 4. Admin Health UI (P1) — RHIDP-15336, RHIDP-15339 -- [ ] 4.1 Create `IngestionHealthPanel.tsx` component in `workspaces/augment/plugins/augment/src/components/AdminPanels/` +- [ ] 4.1 Create `IngestionHealthPanel.tsx` component in `plugins/boost/src/components/AdminPanels/` - [ ] 4.2 Implement health card rendering with PatternFly `Card`, `CardHeader`, `CardBody` components - [ ] 4.3 Add status badge rendering (PatternFly `Label` with success/warning/danger/outline variants for healthy/degraded/failing/disabled) - [ ] 4.4 Add timestamp rendering with `react-time-ago` (last sync attempt, last successful sync) @@ -64,7 +64,7 @@ ## 6. Force Sync (P1) — RHIDP-15335, RHIDP-15336 - [ ] 6.1 Implement `POST /api/boost/ingestion-health/:connectorId/force-sync` route -- [ ] 6.2 Add connector run state registry (in-memory or via SchedulerService.getTaskStatus()) +- [ ] 6.2 Add connector run state registry (via SchedulerService.getTaskStatus()) - [ ] 6.3 Implement concurrent Force Sync prevention (return 409 if already running) - [ ] 6.4 Implement Force Sync trigger via SchedulerService.triggerTask() or direct provider.run() invocation - [ ] 6.5 Add Force Sync timeout wrapper (configurable via `boost.ingestion.forceSyncTimeout`, default 10 min) From 2f4211eead6642ff604deab80b2b8354b1c3e8b9 Mon Sep 17 00:00:00 2001 From: gabemontero Date: Wed, 22 Jul 2026 14:18:20 -0400 Subject: [PATCH 09/19] fix(boost): RHDHPLAN-1513 address fullsend review findings on PR #3755 - ingestion-audit-metrics/design.md: add permission namespace rationale blockquote explaining ai-catalog.admin vs boost.admin coexistence - ingestion-health-dashboard/design.md: update Decision 4 from three-state to five-state model, adding Degraded and Unknown UX states to match Decision 1 health status enum Assisted-by: claude-opus-4-6 Signed-off-by: gabemontero --- .../openspec/changes/ingestion-audit-metrics/design.md | 2 ++ .../changes/ingestion-health-dashboard/design.md | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md index 4672fa6d888..76d039b88b3 100644 --- a/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md @@ -83,6 +83,8 @@ Audit log channel: RHDH audit log (same as RBAC events). Events persist to local Analytics endpoints serve the Admin Panel Analytics tab (RHDHPLAN-1509). All endpoints RBAC-gated with `ai-catalog.admin` permission. +> **Permission namespace note:** `ai-catalog.admin` is a new permission under the `ai-catalog.*` namespace (RHDHPLAN-1508, RBAC for catalog entity visibility). It coexists with the existing `boost.admin` permission, which gates agent/operational features (agent CRUD, chat, MCP, skills, connector config). The distinction is intentional: `ai-catalog.*` covers catalog asset governance (ingestion analytics, entity visibility, versioning policy), while `boost.*` covers agent and operational concerns outside RHDHPLAN-1505 scope. Both namespaces may converge before feature freeze, but the separation avoids coupling catalog RBAC decisions to agent lifecycle permissions during parallel development. + **Why:** Centralized analytics data layer. Frontend consumes clean JSON payloads without direct DB access. RBAC gating ensures only admins see sensitive sync/quality data. **How to apply:** diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md index e58624a6f8f..57702b3974b 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md @@ -90,13 +90,15 @@ Each card shows: **How to apply:** Add route to `AdminLayout.tsx` routes array. `IngestionHealthPanel.tsx` fetches data from `GET /api/boost/ingestion-health` (returns array of connector health objects). Uses `useSWR` for polling (30s interval). Cards rendered in responsive grid layout (PatternFly `Gallery` component). -### Decision 4: Disconnected-cluster UX — three-state model +### Decision 4: Disconnected-cluster UX — five-state model -The health UI distinguishes between three connector states: +The health UI distinguishes between five connector states (matching Decision 1's health status enum): 1. **Enabled + Healthy**: Green status badge, no error indicators, shows sync timestamps -2. **Enabled + Failing**: Red status badge, error summary, diagnostic guidance, "Force Sync" action -3. **Disabled**: Grey/muted status badge, no error indicators, shows "Disabled" label, no "Force Sync" action +2. **Enabled + Degraded**: Yellow status badge, partial error summary, shows mixed sync results +3. **Enabled + Failing**: Red status badge, error summary, diagnostic guidance, "Force Sync" action +4. **Disabled**: Grey/muted status badge, no error indicators, shows "Disabled" label, no "Force Sync" action +5. **Unknown**: Grey status badge with "?" indicator, shows "Never synced" label, "Force Sync" action available Disabled state is set via connector config (`enabled: false`). Health status derivation skips disabled connectors by default — they are excluded from the API response unless `?includeDisabled=true` is set. When included, disabled connectors render with muted treatment in the UI (grey badge, no error indicators). From a2b08b4fdf759c326aa4bcf411fd1258345eb072 Mon Sep 17 00:00:00 2001 From: gabemontero Date: Thu, 23 Jul 2026 13:53:18 -0400 Subject: [PATCH 10/19] =?UTF-8?q?fix(boost):=20RHDHPLAN-1513=20address=20m?= =?UTF-8?q?areklibra=20review=20=E2=80=94=20permission=20namespace,=20sche?= =?UTF-8?q?ma=20cleanup,=20enum=20alignment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Realign permission namespace: boost.admin → ai-catalog.admin across ingestion-health-dashboard and connector-config-hot-reload specs (RHDHPLAN-1513 is catalog ingestion operations, not agent operations) - Replace requireBoostAdmin() middleware with permissions.authorize() using ai-catalog.admin permission per AGENTS.md framework requirement - Fix health status enum: four-state → five-state model (add degraded), add Unknown to status badge colors and task lists - Fix bare connectors.jira.* → boost.connectors.jira.* in hot-reload-propagation spec and tasks - Remove tls.caFile, credentials.*, namespace from boost.connectors Zod schemas (these are catalog.providers fields, not boost.connectors) - Clarify dual enabled gates: catalog.providers.*.enabled (startup registration) vs boost.connectors.*.enabled (runtime sync-skip) - Fix admin_config → boost_admin_config table name - Clarify schema versioning with leaf-only DB writes and connector-level Zod validation - Fix package name @boost/migration-readiness → @red-hat-developer-hub/backstage-plugin-boost-migration-readiness Co-Authored-By: Claude Opus 4.6 Signed-off-by: gabemontero --- .../connector-config-hot-reload/design.md | 27 +++++++---------- .../specs/config-admin-ui/spec.md | 6 ++-- .../specs/config-schemas/spec.md | 30 ++++++++++--------- .../specs/hot-reload-propagation/spec.md | 16 +++++----- .../connector-config-hot-reload/tasks.md | 10 +++---- .../ingestion-health-dashboard/proposal.md | 4 +-- .../specs/admin-health-ui/spec.md | 7 +++-- .../specs/health-status-api/spec.md | 16 +++++----- .../ingestion-health-dashboard/tasks.md | 8 ++--- .../upstream-schema-alignment/proposal.md | 2 +- 10 files changed, 62 insertions(+), 64 deletions(-) diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md index 5be5ec709ec..585f5c542b6 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md @@ -72,25 +72,20 @@ await this.syncClient.connect(endpoint); ### Decision 2: configScope annotation strategy -Each connector config field is annotated with `configScope` to control which layer it lives in: - -| Field | configScope | Rationale | -| ----------------------- | ---------------- | ----------------------------------------------------------------------------------------------------- | -| `enabled` | `db-overridable` | Admin can toggle without YAML change | -| `endpoint` | `db-overridable` | Admin can switch environments without deployment | -| `schedule.intervalMs` | `db-overridable` | Admin can tune sync frequency at runtime | -| `credentials.secretRef` | `yaml-only` | K8s Secret references are deployment-time config | -| `credentials.secretKey` | `yaml-only` | K8s Secret key names are deployment-time config | -| `schedule.cron` | `db-overridable` | Admin can change cron schedule at runtime | -| `namespace` | `yaml-only` | Namespace is deployment-time config (can't change active provider's target namespace without restart) | -| `batchSize` | `db-overridable` | Admin can tune performance at runtime | -| `timeout.connectionMs` | `db-overridable` | Admin can adjust for network conditions at runtime | +Each `boost.connectors..*` field is `configScope: db-overridable` — these are the runtime-tunable fields. Deployment-time fields (`tls.caFile`, `credentials.*`, `namespace`) live under `catalog.providers..*` and are not part of this schema (see Goals namespace table above). -**Runtime state lives in the health store, not the config resolver:** Fields like `lastSyncTimestamp`, `lastSyncOutcome`, and `runStatus` are pure runtime state owned by the `boost_sync_attempts` table (see ingestion-health-dashboard Decision 1). They are not config — they are operational state written by providers after each sync. Querying them goes through the health API (`GET /api/boost/ingestion-health`), not `RuntimeConfigResolver`. +| Field | configScope | Rationale | +| ---------------------- | ---------------- | -------------------------------------------------- | +| `enabled` | `db-overridable` | Admin can toggle without YAML change | +| `endpoint` | `db-overridable` | Admin can switch environments without deployment | +| `schedule.intervalMs` | `db-overridable` | Admin can tune sync frequency at runtime | +| `schedule.cron` | `db-overridable` | Admin can change cron schedule at runtime | +| `batchSize` | `db-overridable` | Admin can tune performance at runtime | +| `timeout.connectionMs` | `db-overridable` | Admin can adjust for network conditions at runtime | -**Why not make everything db-overridable:** Mount paths and Secret references can't change at runtime without a pod restart. Making them `db-overridable` would create false expectations of hot-reload capability. +**Runtime state lives in the health store, not the config resolver:** Fields like `lastSyncTimestamp`, `lastSyncOutcome`, and `runStatus` are pure runtime state owned by the `boost_sync_attempts` table (see ingestion-health-dashboard Decision 1). They are not config — they are operational state written by providers after each sync. Querying them goes through the health API (`GET /api/boost/ingestion-health`), not `RuntimeConfigResolver`. -**Why not make everything yaml-only:** Enable/disable and endpoint changes are the primary use case for hot-reload. Requiring YAML edits defeats the purpose. +**Why all fields are db-overridable:** The `boost.connectors` schema only contains runtime-tunable fields by design. Deployment-time fields (mount paths, Secret references, namespace) belong to `catalog.providers` — they can't change at runtime without a pod restart, so they are excluded from this schema entirely rather than marked `yaml-only`. ### Decision 3: Propagation mechanism — polling-based via reconciliation cycles diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md index c4919aedfc7..50d46df01ff 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md @@ -104,7 +104,7 @@ Admin UI saves connector config changes via `AdminConfigService` with schema val } ``` - **AND** backend `AdminConfigService.setOverride()` validates via Jira connector Zod schema -- **AND** backend writes DB override to `admin_config` table +- **AND** backend writes DB override to `boost_admin_config` table - **AND** backend calls `RuntimeConfigResolver.invalidate()` (whole-cache invalidation, no key parameter) - **AND** backend returns success response - **AND** frontend displays success notification @@ -148,14 +148,14 @@ Admin UI connector config section requires admin role (RBAC permission check). #### Scenario: Admin user accesses connector config -- **WHEN** user with `boost.admin` permission opens connector config section +- **WHEN** user with `ai-catalog.admin` permission opens connector config section - **THEN** UI displays full connector config form with edit capabilities - **AND** all fields (except yaml-only) are editable - **AND** "Save" button is enabled #### Scenario: Non-admin user blocked from connector config -- **WHEN** user without `boost.admin` permission attempts to access connector config section +- **WHEN** user without `ai-catalog.admin` permission attempts to access connector config section - **THEN** UI displays permission error: "You do not have permission to configure connectors" - **AND** config section is not rendered - **AND** user is redirected to unauthorized page diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md index a3e6e0bf22d..0b49a0d97ab 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md @@ -13,20 +13,20 @@ Each connector has a Zod schema defining all configuration fields with `configSc #### Scenario: Jira connector config schema - **WHEN** Jira connector config schema is defined -- **THEN** schema includes fields: `enabled` (boolean), `endpoint` (URL string), `schedule.intervalMs` (number), `schedule.cron` (string), `tls.caFile` (string), `credentials.secretRef` (string), `credentials.secretKey` (string), `namespace` (string), `batchSize` (number), `timeout.connectionMs` (number) -- **AND** each field is annotated with `configScope`: `enabled` is `db-overridable`, `endpoint` is `db-overridable`, `schedule.intervalMs` is `db-overridable`, `schedule.cron` is `db-overridable`, `tls.caFile` is `yaml-only`, `credentials.secretRef` is `yaml-only`, `credentials.secretKey` is `yaml-only`, `namespace` is `yaml-only`, `batchSize` is `db-overridable`, `timeout.connectionMs` is `db-overridable` +- **THEN** schema includes `boost.connectors` fields only: `enabled` (boolean), `endpoint` (URL string), `schedule.intervalMs` (number), `schedule.cron` (string), `batchSize` (number), `timeout.connectionMs` (number) +- **AND** all fields are `configScope: db-overridable` (deployment-time fields like `tls.caFile`, `credentials.*`, and `namespace` live under `catalog.providers..*` and are not part of this schema) #### Scenario: GitHub connector config schema - **WHEN** GitHub connector config schema is defined -- **THEN** schema includes fields: `enabled` (boolean), `endpoint` (URL string), `schedule.intervalMs` (number), `credentials.secretRef` (string), `credentials.secretKey` (string), `namespace` (string), `batchSize` (number) -- **AND** `configScope` annotations match Jira pattern: `enabled`, `endpoint`, `schedule.intervalMs`, `batchSize` are `db-overridable`; `credentials.*`, `namespace` are `yaml-only` +- **THEN** schema includes `boost.connectors` fields only: `enabled` (boolean), `endpoint` (URL string), `schedule.intervalMs` (number), `batchSize` (number) +- **AND** all fields are `configScope: db-overridable` (matching Jira pattern) #### Scenario: GitLab connector config schema - **WHEN** GitLab connector config schema is defined -- **THEN** schema includes fields: `enabled` (boolean), `endpoint` (URL string), `schedule.intervalMs` (number), `credentials.secretRef` (string), `credentials.secretKey` (string), `namespace` (string), `batchSize` (number) -- **AND** `configScope` annotations match Jira pattern +- **THEN** schema includes `boost.connectors` fields only: `enabled` (boolean), `endpoint` (URL string), `schedule.intervalMs` (number), `batchSize` (number) +- **AND** all fields are `configScope: db-overridable` (matching Jira pattern) ### Requirement: RuntimeConfigResolver Integration @@ -45,11 +45,11 @@ Each connector has a Zod schema defining all configuration fields with `configSc - **WHEN** YAML config has `enabled: true` and DB override has `enabled: false` - **THEN** `RuntimeConfigResolver.resolve('boost.connectors.jira.enabled')` returns `false` -#### Scenario: YAML-only field rejects DB override +#### Scenario: Deployment-time field rejects DB override -- **WHEN** admin attempts to write DB override for `credentials.secretRef` -- **THEN** schema validation rejects the write because `credentials.secretRef` has `configScope: yaml-only` -- **AND** admin receives error: "Field credentials.secretRef is yaml-only and cannot be overridden at runtime" +- **WHEN** admin attempts to write DB override for a deployment-time field (e.g., `boost.connectors.jira.credentials.secretRef`) +- **THEN** the write is rejected because `credentials.*`, `tls.*`, and `namespace` are `catalog.providers` fields not present in the `boost.connectors` Zod schema +- **AND** admin receives error: "Unknown config key: credentials.secretRef is not a valid boost.connectors field" ### Requirement: Validation Rejection @@ -77,11 +77,13 @@ Schema validation rejects invalid connector config values before write. Connector config schemas support versioning for backward compatibility. -#### Scenario: Schema version stored with DB override +#### Scenario: Schema version stored with connector-level DB overrides -- **WHEN** admin writes DB override for `boost.connectors.jira` -- **THEN** DB entry includes schema version (e.g., `schemaVersion: 1`) -- **AND** future reads validate against matching schema version +- **WHEN** admin writes DB override for a leaf key under `boost.connectors.jira` (e.g., `boost.connectors.jira.enabled`) +- **THEN** the DB entry stores the leaf key and value (each write targets a single `BoostConfigKey`) +- **AND** the connector-level `schemaVersion` (e.g., `schemaVersion: 1`) is stored alongside the leaf entries for migration tracking +- **AND** `GET /api/boost/admin/config?key=boost.connectors.jira` returns all leaf overrides matching that prefix +- **AND** Zod schema validation is applied at the connector level (aggregating all leaf values) to ensure cross-field consistency #### Scenario: Schema migration on version mismatch diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/hot-reload-propagation/spec.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/hot-reload-propagation/spec.md index e78567c1a22..a459a6186c4 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/hot-reload-propagation/spec.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/hot-reload-propagation/spec.md @@ -12,7 +12,7 @@ Enable/disable changes propagate to active entity provider within 30s + reconcil #### Scenario: Disable connector via DB override -- **WHEN** admin writes DB override `connectors.jira.enabled: false` +- **WHEN** admin writes DB override `boost.connectors.jira.enabled: false` - **THEN** `RuntimeConfigResolver` cache is invalidated immediately - **AND** next entity provider reconciliation cycle (within 5 minutes) reads config - **AND** provider sees `enabled: false`, logs "Jira connector disabled via runtime config, skipping sync" @@ -21,14 +21,14 @@ Enable/disable changes propagate to active entity provider within 30s + reconcil #### Scenario: Re-enable connector via DB override -- **WHEN** admin writes DB override `connectors.jira.enabled: true` after previously disabling +- **WHEN** admin writes DB override `boost.connectors.jira.enabled: true` after previously disabling - **THEN** next reconciliation cycle reads config via `RuntimeConfigResolver` - **AND** provider sees `enabled: true`, resumes normal sync - **AND** entities ingested as usual #### Scenario: Enable/disable with no DB override (YAML baseline only) -- **WHEN** no DB override exists for `connectors.jira.enabled` +- **WHEN** no DB override exists for `boost.connectors.jira.enabled` - **THEN** `RuntimeConfigResolver` returns YAML baseline value (e.g., `enabled: true`) - **AND** provider uses YAML baseline @@ -38,7 +38,7 @@ Schedule changes take effect on next reconciliation cycle. #### Scenario: Increase schedule interval via DB override -- **WHEN** admin writes DB override `connectors.jira.schedule.intervalMs: 600000` (10 minutes) +- **WHEN** admin writes DB override `boost.connectors.jira.schedule.intervalMs: 600000` (10 minutes) - **THEN** current reconciliation cycle completes using old schedule (5 minutes) - **AND** next reconciliation cycle reads new config via `RuntimeConfigResolver` - **AND** provider reschedules task with new interval (10 minutes) @@ -46,14 +46,14 @@ Schedule changes take effect on next reconciliation cycle. #### Scenario: Decrease schedule interval via DB override -- **WHEN** admin writes DB override `connectors.jira.schedule.intervalMs: 60000` (1 minute) +- **WHEN** admin writes DB override `boost.connectors.jira.schedule.intervalMs: 60000` (1 minute) - **THEN** next reconciliation cycle reads new config - **AND** provider reschedules task with new interval (1 minute) - **AND** subsequent cycles run every 1 minute #### Scenario: Switch from interval to cron schedule -- **WHEN** admin writes DB override `connectors.jira.schedule.cron: "0 */2 * * *"` (every 2 hours) and removes `schedule.intervalMs` +- **WHEN** admin writes DB override `boost.connectors.jira.schedule.cron: "0 */2 * * *"` (every 2 hours) and removes `schedule.intervalMs` - **THEN** next reconciliation cycle reads new config - **AND** provider switches from interval-based to cron-based scheduling - **AND** subsequent cycles run at cron-specified times @@ -64,7 +64,7 @@ Endpoint URL changes take effect on next sync cycle. #### Scenario: Change Jira endpoint URL via DB override -- **WHEN** admin writes DB override `connectors.jira.endpoint: "https://jira-staging.example.com"` +- **WHEN** admin writes DB override `boost.connectors.jira.endpoint: "https://jira-staging.example.com"` - **THEN** current sync cycle completes using old endpoint - **AND** next sync cycle reads new config via `RuntimeConfigResolver` - **AND** provider connects to new endpoint `https://jira-staging.example.com` @@ -72,7 +72,7 @@ Endpoint URL changes take effect on next sync cycle. #### Scenario: Invalid endpoint URL rejected before propagation -- **WHEN** admin attempts to write DB override `connectors.jira.endpoint: "not-a-url"` +- **WHEN** admin attempts to write DB override `boost.connectors.jira.endpoint: "not-a-url"` - **THEN** Zod schema validation rejects the write before cache invalidation - **AND** no cache invalidation occurs, provider continues using old endpoint diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md index b51f69648bd..1267f07df5c 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md @@ -2,8 +2,8 @@ ## 1. Zod Schema Definitions (P0) — RHIDP-15340 -- [ ] 1.1 Define Jira connector config Zod schema with fields: `enabled` (boolean), `endpoint` (URL), `schedule.intervalMs` (number), `schedule.cron` (string), `tls.caFile` (string), `credentials.secretRef` (string), `credentials.secretKey` (string), `namespace` (string), `batchSize` (number), `timeout.connectionMs` (number) -- [ ] 1.2 Annotate each Jira config field with `configScope`: `enabled`, `endpoint`, `schedule.*`, `batchSize`, `timeout.*` → `db-overridable`; `credentials.*`, `namespace` → `yaml-only` +- [ ] 1.1 Define Jira connector config Zod schema with `boost.connectors` fields only: `enabled` (boolean), `endpoint` (URL), `schedule.intervalMs` (number), `schedule.cron` (string), `batchSize` (number), `timeout.connectionMs` (number). Note: `tls.caFile`, `credentials.*`, and `namespace` are `catalog.providers` fields — not part of the `boost.connectors` schema. +- [ ] 1.2 All `boost.connectors` fields are `configScope: db-overridable` (deployment-time fields like `credentials.*`, `tls.*`, and `namespace` live under `catalog.providers..*`) - [ ] 1.3 Define GitHub connector config Zod schema with same field pattern - [ ] 1.4 Define GitLab connector config Zod schema with same field pattern - [ ] 1.5 Add URL validation for `endpoint` field (must be valid https:// URL) @@ -15,7 +15,7 @@ ## 2. RuntimeConfigResolver Extension (P0) — RHIDP-15340 -- [ ] 2.1 Extend `RuntimeConfigResolver` to support connector config scope (e.g., `connectors.jira`, `connectors.github`) +- [ ] 2.1 Extend `RuntimeConfigResolver` to support connector config scope (e.g., `boost.connectors.jira`, `boost.connectors.github`) - [ ] 2.2 Extend `resolve(key: BoostConfigKey)` method to support connector leaf config keys (e.g., `boost.connectors.jira.enabled`) - [ ] 2.3 Implement two-layer merge: YAML baseline from `ConfigApi` + DB overrides from `AdminConfigService` - [ ] 2.4 Implement cache with 30s TTL for merged connector config @@ -51,7 +51,7 @@ - [ ] 4.6 Implement save handler: call `POST /api/boost/admin/config` with connector key and updated fields - [ ] 4.7 Implement success notification: "Saved — will take effect within 30 seconds + next reconciliation cycle" - [ ] 4.8 Implement validation error feedback: display server-side Zod validation errors inline -- [ ] 4.9 Implement RBAC gating: require `boost.admin` permission to access connector config section +- [ ] 4.9 Implement RBAC gating: require `ai-catalog.admin` permission to access connector config section (via `permissions.authorize()`) - [ ] 4.10 Implement read-only view for non-admin users (if configured) - [ ] 4.11 Implement config change history view: display last 10 changes from audit log (timestamp, fields, old/new values, user) - [ ] 4.12 Add UI tests: toggle connector, verify POST request with correct payload @@ -90,7 +90,7 @@ - [ ] 8.4 Document propagation latency: 30s TTL + reconciliation interval - [ ] 8.5 Document credential rotation workflow and latency (≤60s kubelet + reconciliation interval) - [ ] 8.6 Add troubleshooting guide: "Config change not taking effect?" → check cache TTL, reconciliation schedule -- [ ] 8.7 Document RBAC permissions required for connector config access (`boost.admin`) +- [ ] 8.7 Document RBAC permissions required for connector config access (`ai-catalog.admin`) ## 9. Schema Migration (P2) diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/proposal.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/proposal.md index b3a1b10dab5..b218e91caaf 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/proposal.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/proposal.md @@ -19,7 +19,7 @@ Air-gapped deployments make connectivity failures common and hard to diagnose. A - REST API exposing per-connector health: enabled state, last sync attempt/success timestamps, health status (healthy/degraded/failing/unknown), most recent error - Data model tracks sync attempts with timestamps and outcomes (success/failure, assets added/updated/removed counts, error type, error message) - Health status derivation: healthy = last 3 sync attempts succeeded, degraded = mixed results (not all-success and not all-failure), failing = last 3 sync attempts failed -- RBAC-gated via boost admin permissions +- RBAC-gated via `ai-catalog.admin` permission - Force Sync API endpoint triggers provider's `run()` method outside scheduled cadence with configurable timeout ### Admin Health UI @@ -44,7 +44,7 @@ Air-gapped deployments make connectivity failures common and hard to diagnose. A ### Disconnected-Cluster Health View Differentiation -- Four-state model: enabled+healthy, enabled+failing, disabled, unknown (never synced) +- Five-state model: enabled+healthy, enabled+degraded, enabled+failing, disabled, unknown (never synced) - Disabled connectors shown with grey/muted treatment (not error state) - Enabled+failing connectors shown with red/alert indicators - No "disabled" state generates alerts or error indicators — prevents false alarms in air-gapped environments diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/admin-health-ui/spec.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/admin-health-ui/spec.md index ed37f91696b..81a64b27c6c 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/admin-health-ui/spec.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/admin-health-ui/spec.md @@ -15,7 +15,7 @@ Health cards display connector status in admin panel. - **WHEN** the Ingestion Health admin panel loads - **THEN** each configured connector renders as a PatternFly `Card` with `CardHeader` and `CardBody` - **AND** the card header shows connector name, type, and status badge (PatternFly `Label` with variant `success`/`warning`/`danger`/`outline`) -- **AND** status badge colors: green for Healthy, yellow for Degraded, red for Failing, grey for Disabled +- **AND** status badge colors: green for Healthy, yellow for Degraded, red for Failing, grey for Disabled, grey with "?" indicator for Unknown #### Scenario: Health card shows timestamps @@ -85,10 +85,11 @@ Disabled connectors visually distinct from failing ones. #### Scenario: Disabled connector uses muted treatment -- **WHEN** a connector is disabled (config `enabled: false`) -- **THEN** the health card renders with grey/muted status badge (`variant="outline"` PatternFly Label) +- **WHEN** a connector has `boost.connectors.*.enabled: false` (runtime sync-skip via `RuntimeConfigResolver`) +- **THEN** the health card renders with grey/muted status badge (`variant="outline"` PatternFly Label) showing "Disabled" - **AND** no red/error indicators are shown - **AND** the card body shows "Connector is disabled. Enable in connector config to resume sync." message +- **AND** note: connectors with `catalog.providers.*.enabled: false` (startup registration gate) are never registered and absent from the UI entirely #### Scenario: Failing connector uses alert treatment diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/health-status-api/spec.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/health-status-api/spec.md index 4ceba83845d..d2cf9cea500 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/health-status-api/spec.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/health-status-api/spec.md @@ -2,7 +2,7 @@ > **Status: Draft** — Pre-implementation specification. Subject to change during implementation. -REST API exposing per-connector health status and force-sync capabilities. Data model tracks sync attempts with timestamps and outcomes. API is RBAC-gated via boost admin permissions. +REST API exposing per-connector health status and force-sync capabilities. Data model tracks sync attempts with timestamps and outcomes. API is RBAC-gated via `ai-catalog.admin` permission. ## ADDED Requirements @@ -12,7 +12,7 @@ The API exposes connector health state for admin dashboard consumption. #### Scenario: API returns per-connector health objects -- **WHEN** `GET /api/boost/ingestion-health` is called with valid boost admin credentials +- **WHEN** `GET /api/boost/ingestion-health` is called with valid `ai-catalog.admin` credentials - **THEN** the response is a JSON array of connector health objects - **AND** each object contains: `connectorId`, `connectorType`, `enabled`, `status` (healthy/degraded/failing/disabled/unknown), `lastSyncAttempt` (ISO timestamp), `lastSuccessfulSync` (ISO timestamp or null), `errorSummary` (object or null), `metrics` (object with assetsAdded/Updated/Removed counts) - **AND** disabled connectors are excluded from the response unless `?includeDisabled=true` query parameter is set @@ -55,19 +55,19 @@ Each connector sync attempt is recorded in the database. - **THEN** `assets_added`, `assets_updated`, `assets_removed` counts are populated based on connector provider's diff logic - **AND** if the provider doesn't report metrics, all three fields default to 0 -### Requirement: RBAC Gating via Boost Admin Permissions +### Requirement: RBAC Gating via `ai-catalog.admin` Permission -Access to the health API is restricted to boost admins. +Access to the health API is restricted to users with `ai-catalog.admin` permission. #### Scenario: Unauthorized user receives 403 -- **WHEN** `GET /api/boost/ingestion-health` is called without boost admin permissions +- **WHEN** `GET /api/boost/ingestion-health` is called without `ai-catalog.admin` permission - **THEN** the response is HTTP 403 Forbidden with error message `"Insufficient permissions to view ingestion health"` - **AND** the request is logged in the audit log (per RHDHPLAN-1508 RHIDP-15277 audit logging pattern) #### Scenario: Admin user receives health data -- **WHEN** `GET /api/boost/ingestion-health` is called with valid boost admin credentials +- **WHEN** `GET /api/boost/ingestion-health` is called with valid `ai-catalog.admin` credentials - **THEN** the response is HTTP 200 OK with connector health array - **AND** the request is logged in the audit log with user identity and timestamp @@ -77,7 +77,7 @@ Admins can manually trigger connector sync outside scheduled cadence. #### Scenario: Force Sync triggers connector run -- **WHEN** `POST /api/boost/ingestion-health/:connectorId/force-sync` is called with valid boost admin credentials +- **WHEN** `POST /api/boost/ingestion-health/:connectorId/force-sync` is called with valid `ai-catalog.admin` credentials - **THEN** the connector provider's `run()` method is invoked immediately - **AND** the response includes a `runId` for polling status - **AND** the sync attempt is recorded in the `boost_sync_attempts` table upon completion @@ -107,7 +107,7 @@ Neo4j Knowledge Graph Sync Adapter health exposed via dedicated endpoint. #### Scenario: Neo4j sync status retrieved -- **WHEN** `GET /api/boost/ingestion-health/neo4j` is called with valid boost admin credentials +- **WHEN** `GET /api/boost/ingestion-health/neo4j` is called with valid `ai-catalog.admin` credentials - **THEN** the response includes: `lastSyncTimestamp`, `outcome` (success/failure), `nodeCount`, `relationshipCount`, `errorSummary` (null if success) - **AND** counts represent the current state of the Neo4j graph (not delta) diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/tasks.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/tasks.md index e21ec5250be..c8c9c6e3f7b 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/tasks.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/tasks.md @@ -13,9 +13,9 @@ - [ ] 2.1 Define `ConnectorHealthStatus` type in `plugins/boost-common/src/types/ingestion-health.ts` (connectorId, connectorType, enabled, status, lastSyncAttempt, lastSuccessfulSync, errorSummary, metrics) - [ ] 2.2 Implement `GET /api/boost/ingestion-health` route returning array of connector health objects -- [ ] 2.3 Implement health status derivation logic in `HealthStatusService.deriveStatus(attempts)` (healthy/degraded/failing based on last 3 attempts) +- [ ] 2.3 Implement health status derivation logic in `HealthStatusService.deriveStatus(attempts)` (healthy/degraded/failing/unknown based on last 3 attempts; unknown = zero sync attempts recorded) - [ ] 2.4 Add `?includeDisabled=true` query parameter support for disabled connectors -- [ ] 2.5 Implement RBAC gating via boost admin permissions check in route handler +- [ ] 2.5 Implement RBAC gating via `ai-catalog.admin` permission check in route handler (using `permissions.authorize()`) - [ ] 2.6 Add audit logging for health API requests (per RHDHPLAN-1508 RHIDP-15277 audit logging pattern) - [ ] 2.7 Implement empty state handling (returns `[]` for zero connectors) - [ ] 2.8 Add health API integration tests (authorized/unauthorized, enabled/disabled filters, health status derivation) @@ -37,7 +37,7 @@ - [ ] 4.1 Create `IngestionHealthPanel.tsx` component in `plugins/boost/src/components/AdminPanels/` - [ ] 4.2 Implement health card rendering with PatternFly `Card`, `CardHeader`, `CardBody` components -- [ ] 4.3 Add status badge rendering (PatternFly `Label` with success/warning/danger/outline variants for healthy/degraded/failing/disabled) +- [ ] 4.3 Add status badge rendering (PatternFly `Label` with success/warning/danger/outline variants for healthy/degraded/failing/disabled/unknown) - [ ] 4.4 Add timestamp rendering with `react-time-ago` (last sync attempt, last successful sync) - [ ] 4.5 Add sync metrics display (assets added/updated/removed counts) - [ ] 4.6 Add error summary section with error type badge and diagnostic guidance text @@ -91,7 +91,7 @@ ## 9. RBAC and Permissions (P1) -- [ ] 9.1 Define boost admin permission check function (`requireBoostAdmin()` middleware) +- [ ] 9.1 Implement RBAC permission check via `permissions.authorize()` with `ai-catalog.admin` permission (not custom middleware — per AGENTS.md, authorization goes through the permission framework) - [ ] 9.2 Add permission check to all ingestion health API routes (GET health, POST force-sync, GET Neo4j, POST Neo4j force-sync) - [ ] 9.3 Add 403 error handling in UI (show "Insufficient permissions" message) - [ ] 9.4 Add permission check integration tests (verify 403 for non-admin users) diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md index 05c205db6ec..1b74398c3bb 100644 --- a/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md @@ -53,6 +53,6 @@ The tool is a scaffold — it establishes the structure for migration-readiness ## Impact - **Documentation:** New annotation specification document in `workspaces/boost/specifications/` directory -- **CLI Tooling:** New `@boost/migration-readiness` package with read-only catalog analysis +- **CLI Tooling:** New `@red-hat-developer-hub/backstage-plugin-boost-migration-readiness` package with read-only catalog analysis - **No Production Changes:** No modifications to existing catalog entities, no catalog processor changes - **Customer Communication:** Migration-readiness assessment available, but actual migration is explicit future work From 99fad1df29019362d956d2784dd46f0ee85c425b Mon Sep 17 00:00:00 2001 From: gabemontero Date: Thu, 23 Jul 2026 14:25:15 -0400 Subject: [PATCH 11/19] =?UTF-8?q?fix(boost):=20RHDHPLAN-1513=20address=20f?= =?UTF-8?q?ullsend=20review=20=E2=80=94=20entity=20mapping,=20cross-ref,?= =?UTF-8?q?=20field=20subset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Model Server mapping: Component/model-server → Resource/ai-model-server (aligns with ai-catalog-entity-model/design.md Decision 1 on main) - Add note: will pivot to upstream Backstage API extension (backstage/backstage#34476) if it becomes available - Cross-reference: catalog-entities/spec.md → ai-catalog-entity-model/design.md Decision 1 (covers all 7 category values) - Tasks 1.3/1.4: 'same field pattern' → connector-appropriate field subset (GitHub/GitLab have 4 fields vs Jira's 6) Co-Authored-By: Claude Opus 4.6 Signed-off-by: gabemontero --- .../changes/connector-config-hot-reload/tasks.md | 4 ++-- .../changes/upstream-schema-alignment/proposal.md | 4 +++- .../specs/annotation-specification/spec.md | 12 ++++++------ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md index 1267f07df5c..e3196967671 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md @@ -4,8 +4,8 @@ - [ ] 1.1 Define Jira connector config Zod schema with `boost.connectors` fields only: `enabled` (boolean), `endpoint` (URL), `schedule.intervalMs` (number), `schedule.cron` (string), `batchSize` (number), `timeout.connectionMs` (number). Note: `tls.caFile`, `credentials.*`, and `namespace` are `catalog.providers` fields — not part of the `boost.connectors` schema. - [ ] 1.2 All `boost.connectors` fields are `configScope: db-overridable` (deployment-time fields like `credentials.*`, `tls.*`, and `namespace` live under `catalog.providers..*`) -- [ ] 1.3 Define GitHub connector config Zod schema with same field pattern -- [ ] 1.4 Define GitLab connector config Zod schema with same field pattern +- [ ] 1.3 Define GitHub connector config Zod schema with connector-appropriate field subset (`enabled`, `endpoint`, `schedule.intervalMs`, `batchSize`) +- [ ] 1.4 Define GitLab connector config Zod schema with connector-appropriate field subset (`enabled`, `endpoint`, `schedule.intervalMs`, `batchSize`) - [ ] 1.5 Add URL validation for `endpoint` field (must be valid https:// URL) - [ ] 1.6 Add positive number validation for `schedule.intervalMs`, `batchSize`, `timeout.connectionMs` - [ ] 1.7 Add cron expression validation for `schedule.cron` (via cron parser library) diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md index 1b74398c3bb..f7e0e0b034d 100644 --- a/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md @@ -48,7 +48,9 @@ The tool is a scaffold — it establishes the structure for migration-readiness | Skill | AIResource | skill | (no RFC yet) | Low | | MCP Server | API | mcp-server | McpServer (RFC #32062) | Medium | | AI Model | Resource | ai-model | ai-model (RFC #33060) | Medium | -| Model Server | Component | model-server | ai-model-server (RFC #33060) | Medium | +| Model Server | Resource | ai-model-server | ai-model-server (RFC #33060) | Medium | + +> **Note:** If the upstream Backstage API extension for capturing AI model servers ([backstage/backstage#34476](https://github.com/backstage/backstage/pull/34476)) becomes available, the Model Server mapping will pivot to use that upstream kind instead of the current `Resource` mapping. ## Impact diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md index 25bd501d4ab..7507e5a5283 100644 --- a/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md @@ -33,7 +33,7 @@ Formal specification of all RHDH AI Asset annotations and entity kinds, with exp - `skill` (AIResource kind) - `mcp-server` (API kind) - `ai-model` (Resource kind) -- `model-server` (Component kind) +- `model-server` (Resource kind) - Each type includes: entity kind pairing, purpose, examples --- @@ -59,7 +59,7 @@ Formal specification of all RHDH AI Asset annotations and entity kinds, with exp **THEN** the mapping from RHDH AI Model and Model Server entities to RFC #33060 is documented: - **AI Model:** Current `kind: Resource`, `spec.type: ai-model` → Target `kind: ai-model` (RFC #33060) -- **Model Server:** Current `kind: Component`, `spec.type: model-server` → Target `kind: ai-model-server` (RFC #33060) +- **Model Server:** Current `kind: Resource`, `spec.type: ai-model-server` → Target `kind: ai-model-server` (RFC #33060). Note: if the upstream Backstage API extension ([backstage/backstage#34476](https://github.com/backstage/backstage/pull/34476)) becomes available, the Model Server mapping will pivot to that upstream kind. - Transformation requirements listed per entity type - Confidence level: Medium (RFC active but schema may evolve) @@ -100,12 +100,12 @@ Formal specification of all RHDH AI Asset annotations and entity kinds, with exp - Published in `workspaces/boost/specifications/` directory - Follows the same structure as existing Boost specifications - Includes a last-updated date and draft status header -- Cross-references existing `agent-creation-discovery/catalog-entities` spec +- Cross-references `ai-catalog-entity-model/design.md` Decision 1 as the authoritative entity mapping source --- -## Scenario: Spec references existing catalog-entities entity type strategy +## Scenario: Spec references authoritative entity mapping -**GIVEN** the `agent-creation-discovery` change defined the entity type strategy +**GIVEN** the `ai-catalog-entity-model` change defined the entity mapping table (Decision 1) **WHEN** the annotation specification document is written -**THEN** it cross-references the `catalog-entities/spec.md` entity type strategy table as the source of truth for current RHDH AI Asset entity mappings +**THEN** it cross-references `ai-catalog-entity-model/design.md` Decision 1 as the source of truth for current RHDH AI Asset entity mappings (covers all 7 category values including `skill`, `rule`, `skill-bundle`, and `model-server`) From 7d03fd46f0a3e2c3df06740df6e6c64b1180d9ae Mon Sep 17 00:00:00 2001 From: gabemontero Date: Thu, 23 Jul 2026 15:04:26 -0400 Subject: [PATCH 12/19] fix(boost): RHDHPLAN-1513 add rule and skill-bundle to annotation values and mapping table Align annotation-specification/spec.md and proposal.md with ai-catalog-entity-model/design.md Decision 1 which defines 7 category values. The annotation listing and mapping table previously listed only 5, omitting rule and skill-bundle. Co-Authored-By: Claude Opus 4.6 Signed-off-by: gabemontero --- .../openspec/changes/upstream-schema-alignment/proposal.md | 4 +++- .../specs/annotation-specification/spec.md | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md index f7e0e0b034d..7776ff2ebe4 100644 --- a/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md @@ -19,7 +19,7 @@ The mapping document and dry-run tool make the migration path transparent and me A formal specification covering all RHDH AI Asset annotations and entity kinds: -- `rhdh.io/ai-asset-category` values (agent, skill, mcp-server, ai-model, model-server) +- `rhdh.io/ai-asset-category` values (agent, skill, rule, skill-bundle, mcp-server, ai-model, model-server) - `rhdh.io/ai-asset-version` annotation format and normalization rules - `rhdh.io/ai-asset-source` annotation format - Entity kind + `spec.type` mapping table showing current state → proposed RFC kind @@ -48,6 +48,8 @@ The tool is a scaffold — it establishes the structure for migration-readiness | Skill | AIResource | skill | (no RFC yet) | Low | | MCP Server | API | mcp-server | McpServer (RFC #32062) | Medium | | AI Model | Resource | ai-model | ai-model (RFC #33060) | Medium | +| Rule | AIResource | rule | (no RFC yet) | Low | +| Skill Bundle | AIResource | skill-bundle | (no RFC yet) | Low | | Model Server | Resource | ai-model-server | ai-model-server (RFC #33060) | Medium | > **Note:** If the upstream Backstage API extension for capturing AI model servers ([backstage/backstage#34476](https://github.com/backstage/backstage/pull/34476)) becomes available, the Model Server mapping will pivot to use that upstream kind instead of the current `Resource` mapping. diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md index 7507e5a5283..67e85a3bb89 100644 --- a/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md @@ -16,7 +16,7 @@ Formal specification of all RHDH AI Asset annotations and entity kinds, with exp **WHEN** the specification document is published **THEN** all `rhdh.io/ai-asset-*` annotations are documented: -- `rhdh.io/ai-asset-category` values: `agent`, `skill`, `mcp-server`, `ai-model`, `model-server` +- `rhdh.io/ai-asset-category` values: `agent`, `skill`, `rule`, `skill-bundle`, `mcp-server`, `ai-model`, `model-server` - `rhdh.io/ai-asset-version` format and normalization rules - `rhdh.io/ai-asset-source` format - Each annotation includes: purpose, valid values, format constraints, examples From 149cf4ffe5b436ba8c545ead150b29758ce2adc8 Mon Sep 17 00:00:00 2001 From: gabemontero Date: Thu, 23 Jul 2026 15:26:17 -0400 Subject: [PATCH 13/19] fix(boost): RHDHPLAN-1513 complete 5-to-7 alignment in tasks.md and spec.type list Continue alignment with ai-catalog-entity-model/design.md Decision 1: - tasks.md task 1.1: add rule, skill-bundle to category values - tasks.md task 3.1: 'five' -> 'seven' AI Asset types - annotation-specification/spec.md: add rule, skill-bundle to spec.type list; fix model-server -> ai-model-server spec.type Co-Authored-By: Claude Opus 4.6 Signed-off-by: gabemontero --- .../specs/annotation-specification/spec.md | 4 +++- .../boost/openspec/changes/upstream-schema-alignment/tasks.md | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md index 67e85a3bb89..d90f4956c88 100644 --- a/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md @@ -31,9 +31,11 @@ Formal specification of all RHDH AI Asset annotations and entity kinds, with exp - `ai-agent` (Component kind) - `skill` (AIResource kind) +- `rule` (AIResource kind) +- `skill-bundle` (AIResource kind) - `mcp-server` (API kind) - `ai-model` (Resource kind) -- `model-server` (Resource kind) +- `ai-model-server` (Resource kind) - Each type includes: entity kind pairing, purpose, examples --- diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/tasks.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/tasks.md index 1de9d91b759..1d978d89421 100644 --- a/workspaces/boost/openspec/changes/upstream-schema-alignment/tasks.md +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/tasks.md @@ -2,7 +2,7 @@ ## 1. Annotation Specification Document (P0) — RHIDP-15346 -- [ ] 1.1 Document all `rhdh.io/ai-asset-category` values (agent, skill, mcp-server, ai-model, model-server) +- [ ] 1.1 Document all `rhdh.io/ai-asset-category` values (agent, skill, rule, skill-bundle, mcp-server, ai-model, model-server) - [ ] 1.2 Document `rhdh.io/ai-asset-version` annotation format and normalization rules - [ ] 1.3 Document `rhdh.io/ai-asset-source` annotation format - [ ] 1.4 Document entity kind + `spec.type` mapping table for all AI Asset types @@ -32,7 +32,7 @@ ## 3. Testing (P1) -- [ ] 3.1 Unit test mapping logic with fixture entities (all five AI Asset types) +- [ ] 3.1 Unit test mapping logic with fixture entities (all seven AI Asset types) - [ ] 3.2 Unit test confidence level assignment - [ ] 3.3 Unit test field transformation identification - [ ] 3.4 Integration test against mock catalog API From 187e986392007220b795cecc30cfec4032d6f5d7 Mon Sep 17 00:00:00 2001 From: gabemontero Date: Fri, 24 Jul 2026 11:18:31 -0400 Subject: [PATCH 14/19] =?UTF-8?q?fix(boost):=20RHDHPLAN-1513=20address=20m?= =?UTF-8?q?areklibra=20review=20round=20=E2=80=94=20package=20name,=20stat?= =?UTF-8?q?us=20enum,=20config=20schema=20clarity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace @boost/migration-readiness with @red-hat-developer-hub/backstage-plugin-boost-migration-readiness across tasks.md, design.md, and spec.md (4 locations) + add workspace path - Remove tls/credentials/yaml-only from connector-config-hot-reload/proposal.md (those fields live under catalog.providers, not boost.connectors) - Fix health status enum: remove disabled from status values (healthy/degraded/failing/unknown); disabled is an enabled=false overlay, not a status value — propagated to design.md and tasks.md - Clarify per-leaf Zod validation vs connector-level aggregate validation in config-schemas/spec.md - Clarify schemaVersion storage: explicit __schemaVersion leaf key, bump on migration only, concurrent leaf writes non-conflicting, GET-prefix merge behavior Co-Authored-By: Claude Opus 4.6 Signed-off-by: gabemontero --- .../changes/connector-config-hot-reload/proposal.md | 4 ++-- .../specs/config-schemas/spec.md | 11 ++++++----- .../changes/ingestion-health-dashboard/design.md | 2 +- .../specs/health-status-api/spec.md | 5 +++-- .../changes/ingestion-health-dashboard/tasks.md | 2 +- .../changes/upstream-schema-alignment/design.md | 2 +- .../specs/migration-readiness-tooling/spec.md | 2 +- .../changes/upstream-schema-alignment/tasks.md | 4 ++-- 8 files changed, 17 insertions(+), 15 deletions(-) diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md index 2f01468387d..7ff252b8a13 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md @@ -14,8 +14,8 @@ The key distinction: Backstage's built-in `ConfigApi` loads config at startup wi ### Config Schemas -- Zod schema definitions for per-connector settings: `enabled`, `endpoint`, `schedule`, `tls`, `credentials` -- `configScope` annotation on each field: `yaml-only` (mount paths, K8s Secret references), `db-overridable` (enable/disable, endpoint URL, schedule). Runtime operational state (last sync timestamp, run status) lives in the health store (`boost_sync_attempts` table), not the config resolver. +- Zod schema definitions for per-connector `boost.connectors.*` fields: `enabled`, `endpoint`, `schedule`, `batchSize`, `timeout` — all `configScope: db-overridable`. Deployment-time fields (`tls`, `credentials`, `namespace`) live under `catalog.providers.*` and are not part of these schemas. +- Runtime operational state (last sync timestamp, run status) lives in the health store (`boost_sync_attempts` table), not the config resolver. - Schema validation rejects invalid connector config values before write - Integration with `RuntimeConfigResolver`'s two-layer resolution diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md index 0b49a0d97ab..8ccb2b21fc0 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md @@ -37,7 +37,8 @@ Each connector has a Zod schema defining all configuration fields with `configSc - **WHEN** `RuntimeConfigResolver.resolve('boost.connectors.jira.enabled')` is called - **THEN** resolver reads YAML baseline value from `ConfigApi` at key path `boost.connectors.jira.enabled` - **AND** resolver reads any DB override from `AdminConfigService` for leaf key `boost.connectors.jira.enabled` -- **AND** resolver returns the DB override value if present, otherwise the YAML baseline value, validated against the Jira connector Zod schema +- **AND** resolver returns the DB override value if present, otherwise the YAML baseline value, validated against the leaf field's Zod type (per-leaf validation on resolve/write — not the full connector schema) +- **AND** connector-level aggregate validation (cross-field consistency checks) is applied only on `GET`-prefix queries that return all leaf overrides for a connector, not on individual leaf resolve - **AND** resolved value is cached with 30s TTL #### Scenario: DB override takes precedence over YAML @@ -80,10 +81,10 @@ Connector config schemas support versioning for backward compatibility. #### Scenario: Schema version stored with connector-level DB overrides - **WHEN** admin writes DB override for a leaf key under `boost.connectors.jira` (e.g., `boost.connectors.jira.enabled`) -- **THEN** the DB entry stores the leaf key and value (each write targets a single `BoostConfigKey`) -- **AND** the connector-level `schemaVersion` (e.g., `schemaVersion: 1`) is stored alongside the leaf entries for migration tracking -- **AND** `GET /api/boost/admin/config?key=boost.connectors.jira` returns all leaf overrides matching that prefix -- **AND** Zod schema validation is applied at the connector level (aggregating all leaf values) to ensure cross-field consistency +- **THEN** the DB entry stores the leaf key and value (each write targets a single `BoostConfigKey`); concurrent writes to different leaves under the same connector do not conflict +- **AND** the connector-level schema version is stored as an explicit leaf key `boost.connectors.jira.__schemaVersion` (e.g., value `1`), bumped only during schema migrations — not on individual config writes +- **AND** `GET /api/boost/admin/config?key=boost.connectors.jira` returns all leaf overrides matching that prefix, merged into a single object for the response +- **AND** Zod schema validation is applied at the connector level (aggregating all leaf values) on GET-prefix queries to ensure cross-field consistency #### Scenario: Schema migration on version mismatch diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md index 57702b3974b..829f021dc39 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md @@ -80,7 +80,7 @@ Navigation: Adds "Ingestion Health" item to admin panel sidebar (`AdminLayout.ts Each card shows: - Connector name and type (GitHub, GitLab, Jira) -- Status badge (Healthy/Degraded/Failing/Disabled) +- Status badge (Healthy/Degraded/Failing/Unknown) plus Disabled overlay badge when `enabled: false` - Last sync attempt timestamp (relative time via `react-time-ago`) - Last successful sync timestamp (or "Never" if no success) - Error summary (if failing/degraded) with classification badge diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/health-status-api/spec.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/health-status-api/spec.md index d2cf9cea500..f2e8967a5a8 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/health-status-api/spec.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/health-status-api/spec.md @@ -14,8 +14,9 @@ The API exposes connector health state for admin dashboard consumption. - **WHEN** `GET /api/boost/ingestion-health` is called with valid `ai-catalog.admin` credentials - **THEN** the response is a JSON array of connector health objects -- **AND** each object contains: `connectorId`, `connectorType`, `enabled`, `status` (healthy/degraded/failing/disabled/unknown), `lastSyncAttempt` (ISO timestamp), `lastSuccessfulSync` (ISO timestamp or null), `errorSummary` (object or null), `metrics` (object with assetsAdded/Updated/Removed counts) -- **AND** disabled connectors are excluded from the response unless `?includeDisabled=true` query parameter is set +- **AND** each object contains: `connectorId`, `connectorType`, `enabled`, `status` (healthy/degraded/failing/unknown), `lastSyncAttempt` (ISO timestamp), `lastSuccessfulSync` (ISO timestamp or null), `errorSummary` (object or null), `metrics` (object with assetsAdded/Updated/Removed counts) +- **AND** `status` reflects health derivation only — `disabled` is not a status value; disabled connectors are identified by `enabled: false` +- **AND** disabled connectors (`enabled: false`) are excluded from the response unless `?includeDisabled=true` query parameter is set; when included, their `status` is derived normally from sync history (or `unknown` if never synced) #### Scenario: Health status derivation logic diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/tasks.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/tasks.md index c8c9c6e3f7b..c170f7626f8 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/tasks.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/tasks.md @@ -37,7 +37,7 @@ - [ ] 4.1 Create `IngestionHealthPanel.tsx` component in `plugins/boost/src/components/AdminPanels/` - [ ] 4.2 Implement health card rendering with PatternFly `Card`, `CardHeader`, `CardBody` components -- [ ] 4.3 Add status badge rendering (PatternFly `Label` with success/warning/danger/outline variants for healthy/degraded/failing/disabled/unknown) +- [ ] 4.3 Add status badge rendering (PatternFly `Label` with success/warning/danger/outline variants for healthy/degraded/failing/unknown) plus disabled overlay badge when `enabled: false` - [ ] 4.4 Add timestamp rendering with `react-time-ago` (last sync attempt, last successful sync) - [ ] 4.5 Add sync metrics display (assets added/updated/removed counts) - [ ] 4.6 Add error summary section with error type badge and diagnostic guidance text diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/design.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/design.md index 465527607c0..90ad601583d 100644 --- a/workspaces/boost/openspec/changes/upstream-schema-alignment/design.md +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/design.md @@ -69,7 +69,7 @@ For each RHDH entity type, the mapping table shows: ### Decision 3: Dry-run tool as external CLI command -The dry-run tool runs externally against the catalog API, not as a catalog processor. It's a standalone CLI command: `npx @boost/migration-readiness --catalog-url `. +The dry-run tool runs externally against the catalog API, not as a catalog processor. It's a standalone CLI command: `npx @red-hat-developer-hub/backstage-plugin-boost-migration-readiness --catalog-url ` (workspace path: `plugins/boost-migration-readiness/`). **Why:** External CLI is simpler and safer for read-only analysis. A catalog processor hook is future work once RFCs finalize and actual migration begins. diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/migration-readiness-tooling/spec.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/migration-readiness-tooling/spec.md index 969c46c4ddc..6e5015ac4ee 100644 --- a/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/migration-readiness-tooling/spec.md +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/migration-readiness-tooling/spec.md @@ -13,7 +13,7 @@ Read-only CLI command that enumerates AI Asset catalog entities and reports migr ## Scenario: CLI enumerates AI Asset entities from catalog API **GIVEN** a Backstage catalog with AI Asset entities -**WHEN** the dry-run CLI is executed: `npx @boost/migration-readiness --catalog-url ` +**WHEN** the dry-run CLI is executed: `npx @red-hat-developer-hub/backstage-plugin-boost-migration-readiness --catalog-url ` **THEN** the tool: - Queries the catalog API for all entities diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/tasks.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/tasks.md index 1d978d89421..0a0088f4b3c 100644 --- a/workspaces/boost/openspec/changes/upstream-schema-alignment/tasks.md +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/tasks.md @@ -17,7 +17,7 @@ ## 2. Dry-Run Tooling Scaffold (P1) — RHIDP-15347 -- [ ] 2.1 Create `@boost/migration-readiness` CLI package structure +- [ ] 2.1 Create `@red-hat-developer-hub/backstage-plugin-boost-migration-readiness` CLI package structure (workspace path: `plugins/boost-migration-readiness/`) - [ ] 2.2 Set up TypeScript configuration and build pipeline - [ ] 2.3 Implement catalog API client for entity enumeration - [ ] 2.4 Filter entities by `rhdh.io/ai-asset-category` annotation presence @@ -45,7 +45,7 @@ ## 4. Documentation (P1) -- [ ] 4.1 Write README for `@boost/migration-readiness` package +- [ ] 4.1 Write README for `@red-hat-developer-hub/backstage-plugin-boost-migration-readiness` package - [ ] 4.2 Document how to run dry-run tool (command-line usage) - [ ] 4.3 Document output interpretation (what each field means) - [ ] 4.4 Document confidence levels and their implications From 9d03feb0d1168d4b1fa973fb91c1cf40bfca96ab Mon Sep 17 00:00:00 2001 From: gabemontero Date: Fri, 24 Jul 2026 17:55:51 -0400 Subject: [PATCH 15/19] =?UTF-8?q?fix(boost):=20RHDHPLAN-1513=20align=20spe?= =?UTF-8?q?c.type=20skill-bundle=20=E2=86=92=20ai-skill-bundle=20per=20ent?= =?UTF-8?q?ity=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit proposal.md mapping table and annotation-specification spec.type list both had spec.type: skill-bundle — the authoritative entity model design (ai-catalog-entity-model/design.md Decision 1, on main) defines this as ai-skill-bundle. The rhdh.io/ai-asset-category annotation value remains skill-bundle (correct — category ≠ spec.type). Co-Authored-By: Claude Opus 4.6 Signed-off-by: gabemontero --- .../openspec/changes/upstream-schema-alignment/proposal.md | 2 +- .../specs/annotation-specification/spec.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md index 7776ff2ebe4..15c72cf2604 100644 --- a/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/proposal.md @@ -49,7 +49,7 @@ The tool is a scaffold — it establishes the structure for migration-readiness | MCP Server | API | mcp-server | McpServer (RFC #32062) | Medium | | AI Model | Resource | ai-model | ai-model (RFC #33060) | Medium | | Rule | AIResource | rule | (no RFC yet) | Low | -| Skill Bundle | AIResource | skill-bundle | (no RFC yet) | Low | +| Skill Bundle | AIResource | ai-skill-bundle | (no RFC yet) | Low | | Model Server | Resource | ai-model-server | ai-model-server (RFC #33060) | Medium | > **Note:** If the upstream Backstage API extension for capturing AI model servers ([backstage/backstage#34476](https://github.com/backstage/backstage/pull/34476)) becomes available, the Model Server mapping will pivot to use that upstream kind instead of the current `Resource` mapping. diff --git a/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md b/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md index d90f4956c88..2e9dcef8a64 100644 --- a/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md +++ b/workspaces/boost/openspec/changes/upstream-schema-alignment/specs/annotation-specification/spec.md @@ -32,7 +32,7 @@ Formal specification of all RHDH AI Asset annotations and entity kinds, with exp - `ai-agent` (Component kind) - `skill` (AIResource kind) - `rule` (AIResource kind) -- `skill-bundle` (AIResource kind) +- `ai-skill-bundle` (AIResource kind) - `mcp-server` (API kind) - `ai-model` (Resource kind) - `ai-model-server` (Resource kind) From d548b68fe1d64401c3ff224007e43a7eb13cd338 Mon Sep 17 00:00:00 2001 From: gabemontero Date: Fri, 24 Jul 2026 18:07:40 -0400 Subject: [PATCH 16/19] =?UTF-8?q?fix(boost):=20RHDHPLAN-1513=20cascade=20p?= =?UTF-8?q?ropagation=20=E2=80=94=20errorType=20casing,=20neo4j=20field=20?= =?UTF-8?q?alignment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - error-classification/spec.md: error_type → errorType in THEN clauses (14 occurrences). DB column is snake_case error_type, but ErrorClassifier returns camelCase errorType per the interface at line 105 and tasks.md. - analytics-api/spec.md: neo4j_status.connected → neo4j_connected to match design.md Decision 2 line 113 flat field structure. Co-Authored-By: Claude Opus 4.6 Signed-off-by: gabemontero --- .../specs/analytics-api/spec.md | 4 +-- .../specs/error-classification/spec.md | 28 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/analytics-api/spec.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/analytics-api/spec.md index 0974338db08..0ef2ded1104 100644 --- a/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/analytics-api/spec.md +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/specs/analytics-api/spec.md @@ -38,8 +38,8 @@ **GIVEN** Neo4j is configured and syncing (or not syncing) **WHEN** an admin calls any analytics endpoint (e.g., sync history, quality scores) -**THEN** the response includes a `neo4j_status` object: `{ connected: boolean, last_sync: string, entity_count: number }` -**AND** `connected` reflects the current Neo4j connection status +**THEN** the response includes Neo4j sync status fields: `{ neo4j_connected: boolean, last_sync: string, entity_count: number }` +**AND** `neo4j_connected` reflects the current Neo4j connection status **AND** `last_sync` is the timestamp of the last successful Neo4j sync **AND** `entity_count` is the count of entities in Neo4j (if connected) diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/error-classification/spec.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/error-classification/spec.md index d24626d8ffc..9718263112e 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/error-classification/spec.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/error-classification/spec.md @@ -13,20 +13,20 @@ Authentication and authorization errors identified and classified. #### Scenario: Auth failure classified with diagnostic guidance - **WHEN** a connector sync fails with authentication error (e.g., "401 Unauthorized", "Invalid API token", "OAuth token expired") -- **THEN** the error is classified as `error_type: "auth"` +- **THEN** the error is classified as `errorType: "auth"` - **AND** `diagnosticGuidance` is `"Check service account credentials in connector config. Verify API token is valid and has required permissions."` - **AND** error classification is performed via `ErrorClassifier.classify(error)` utility function #### Scenario: OAuth token expiry detected - **WHEN** a connector sync fails with OAuth-specific error (e.g., "Access token expired", "Refresh token invalid") -- **THEN** the error is classified as `error_type: "auth"` +- **THEN** the error is classified as `errorType: "auth"` - **AND** `diagnosticGuidance` is `"OAuth token expired. Re-authenticate the connector in admin panel or refresh the access token."` #### Scenario: Insufficient permissions detected - **WHEN** a connector sync fails with permission error (e.g., "403 Forbidden", "Insufficient scopes") -- **THEN** the error is classified as `error_type: "auth"` +- **THEN** the error is classified as `errorType: "auth"` - **AND** `diagnosticGuidance` is `"Service account lacks required permissions. Verify API token has read access to required resources (repositories, projects, issues)."` ### Requirement: Network/DNS/Connectivity Failure Classification @@ -36,19 +36,19 @@ Network-related errors identified and classified. #### Scenario: Network failure classified with diagnostic guidance - **WHEN** a connector sync fails with network error (e.g., "ECONNREFUSED", "ETIMEDOUT", "Network unreachable") -- **THEN** the error is classified as `error_type: "network"` +- **THEN** the error is classified as `errorType: "network"` - **AND** `diagnosticGuidance` is `"Network connectivity issue. Verify DNS resolution, firewall rules, and external service availability. In air-gapped clusters, disable connectors for unreachable external services."` #### Scenario: DNS resolution failure detected - **WHEN** a connector sync fails with DNS error (e.g., "ENOTFOUND", "DNS lookup failed") -- **THEN** the error is classified as `error_type: "network"` +- **THEN** the error is classified as `errorType: "network"` - **AND** `diagnosticGuidance` is `"DNS resolution failed. Verify DNS configuration and external service hostname. In disconnected clusters, this is expected for external services—consider disabling the connector."` #### Scenario: TLS/SSL certificate error detected - **WHEN** a connector sync fails with TLS error (e.g., "UNABLE_TO_VERIFY_LEAF_SIGNATURE", "self signed certificate") -- **THEN** the error is classified as `error_type: "network"` +- **THEN** the error is classified as `errorType: "network"` - **AND** `diagnosticGuidance` is `"TLS/SSL certificate verification failed. Verify certificate chain or configure connector to trust custom CA certificates."` ### Requirement: Schema/Parsing Mismatch Classification @@ -58,19 +58,19 @@ Data schema and parsing errors identified and classified. #### Scenario: Schema mismatch classified with diagnostic guidance - **WHEN** a connector sync fails with schema error (e.g., "Unexpected field 'x' in response", "Missing required field 'y'") -- **THEN** the error is classified as `error_type: "schema"` +- **THEN** the error is classified as `errorType: "schema"` - **AND** `diagnosticGuidance` is `"API response schema mismatch. This may indicate an upstream API version change. Check connector logs for expected vs actual schema and consider updating the connector."` #### Scenario: JSON parsing error detected - **WHEN** a connector sync fails with parsing error (e.g., "Unexpected token < in JSON", "SyntaxError: JSON.parse") -- **THEN** the error is classified as `error_type: "schema"` +- **THEN** the error is classified as `errorType: "schema"` - **AND** `diagnosticGuidance` is `"Failed to parse API response. Upstream service may be returning HTML error page or malformed JSON. Check connector logs for raw response body."` #### Scenario: GraphQL query error detected - **WHEN** a connector sync fails with GraphQL error (e.g., "Field 'x' doesn't exist on type 'Y'", "Cannot query field") -- **THEN** the error is classified as `error_type: "schema"` +- **THEN** the error is classified as `errorType: "schema"` - **AND** `diagnosticGuidance` is `"GraphQL query schema mismatch. Upstream GraphQL schema may have changed. Review connector GraphQL queries against current API schema."` ### Requirement: Rate Limiting Classification @@ -80,19 +80,19 @@ Rate limit errors identified and classified. #### Scenario: Rate limit classified with diagnostic guidance - **WHEN** a connector sync fails with rate limit error (e.g., "429 Too Many Requests", "Rate limit exceeded", "X-RateLimit-Remaining: 0") -- **THEN** the error is classified as `error_type: "rate-limit"` +- **THEN** the error is classified as `errorType: "rate-limit"` - **AND** `diagnosticGuidance` is `"API rate limit exceeded. Connector will retry on next scheduled sync. Consider increasing sync interval or requesting higher rate limits from service provider."` #### Scenario: GitHub-specific rate limit detected - **WHEN** a GitHub connector sync fails with rate limit headers (e.g., `X-RateLimit-Remaining: 0`) -- **THEN** the error is classified as `error_type: "rate-limit"` +- **THEN** the error is classified as `errorType: "rate-limit"` - **AND** `diagnosticGuidance` includes retry timestamp from `X-RateLimit-Reset` header (e.g., `"Rate limit will reset at 2:30 PM UTC. Connector will retry automatically."`) #### Scenario: Secondary rate limit detected - **WHEN** a connector sync fails with secondary rate limit (e.g., GitHub "You have exceeded a secondary rate limit") -- **THEN** the error is classified as `error_type: "rate-limit"` +- **THEN** the error is classified as `errorType: "rate-limit"` - **AND** `diagnosticGuidance` is `"Secondary rate limit triggered (too many requests in short period). Connector will back off and retry. Consider reducing sync frequency."` ### Requirement: Shared Classification Logic Across Connectors @@ -124,7 +124,7 @@ Errors that don't match known patterns fall back to generic classification. #### Scenario: Unknown error fallback - **WHEN** a connector sync fails and the error doesn't match any classification patterns -- **THEN** the error is classified as `error_type: "unknown"` +- **THEN** the error is classified as `errorType: "unknown"` - **AND** `diagnosticGuidance` is `"Unknown error occurred. Check connector logs for detailed error trace and stack trace."` - **AND** `errorMessage` contains the raw error string for admin inspection @@ -132,7 +132,7 @@ Errors that don't match known patterns fall back to generic classification. - **WHEN** a connector sync fails with multiple errors (e.g., network timeout after auth failure) - **THEN** the error is classified based on the first classifiable error in the chain -- **AND** if no errors match known patterns, falls back to `error_type: "unknown"` +- **AND** if no errors match known patterns, falls back to `errorType: "unknown"` ### Requirement: Error Classification in Health Cards From fca12c7622d87fb4e747d7beff4fba77be25b238 Mon Sep 17 00:00:00 2001 From: gabemontero Date: Fri, 24 Jul 2026 18:30:18 -0400 Subject: [PATCH 17/19] =?UTF-8?q?fix(boost):=20RHDHPLAN-1513=20correct=20D?= =?UTF-8?q?ecision=204=20parenthetical=20=E2=80=94=20five-state=20model=20?= =?UTF-8?q?extends,=20not=20matches,=20Decision=201=20enum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Decision 1 defines 4 health status values (healthy/degraded/failing/unknown). Decision 4's five-state model adds Disabled as an enabled=false overlay — it extends the enum, not matches it. Co-Authored-By: Claude Opus 4.6 Signed-off-by: gabemontero --- .../boost/openspec/changes/ingestion-health-dashboard/design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md index 829f021dc39..5fc1a383f90 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/design.md @@ -92,7 +92,7 @@ Each card shows: ### Decision 4: Disconnected-cluster UX — five-state model -The health UI distinguishes between five connector states (matching Decision 1's health status enum): +The health UI distinguishes between five connector states (extending Decision 1's health status enum with Disabled as an enabled=false overlay): 1. **Enabled + Healthy**: Green status badge, no error indicators, shows sync timestamps 2. **Enabled + Degraded**: Yellow status badge, partial error summary, shows mixed sync results From 300ac1cdeea3de7607d9c6e577afd3da4182b24c Mon Sep 17 00:00:00 2001 From: gabemontero Date: Mon, 27 Jul 2026 15:27:43 -0400 Subject: [PATCH 18/19] =?UTF-8?q?fix(boost):=20RHDHPLAN-1513=20address=20M?= =?UTF-8?q?arek=20review=20=E2=80=94=20namespace,=20TTL=20wording,=20remov?= =?UTF-8?q?eOverride?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thread 1: catalog.providers → ai-catalog.providers across 7 files (11 edits) Threads 2-4: 'within 30s' → 'cache refresh ≤30s; takes effect on next reconciliation cycle' across 5 files (9 edits) Thread 5: add removeOverride/DELETE mechanism for clearing DB overrides, specify cron-over-intervalMs precedence, add matching UI/tasks/schema specs Co-Authored-By: Claude Opus 4.6 Signed-off-by: gabemontero --- .../connector-config-hot-reload/design.md | 22 ++++++++++--------- .../connector-config-hot-reload/proposal.md | 4 ++-- .../specs/config-admin-ui/spec.md | 14 +++++++++--- .../specs/config-schemas/spec.md | 21 ++++++++++++++++-- .../specs/hot-reload-propagation/spec.md | 15 +++++++++---- .../connector-config-hot-reload/tasks.md | 11 +++++----- .../specs/admin-health-ui/spec.md | 2 +- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md index 585f5c542b6..ac6c0111393 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md @@ -16,16 +16,16 @@ Connector config becomes a new scope under this existing infrastructure. ## Goals -- Extend `RuntimeConfigResolver` to connector settings (runtime sync-skip via `boost.connectors.*.enabled`), not create a new config system. Note: startup registration is governed by `catalog.providers..enabled` at module init time (shared-infra Decision 4) — a provider never registered at startup cannot be hot-enabled at runtime. Hot-reload controls sync behavior of already-registered providers. +- Extend `RuntimeConfigResolver` to connector settings (runtime sync-skip via `boost.connectors.*.enabled`), not create a new config system. Note: startup registration is governed by `ai-catalog.providers..enabled` at module init time (shared-infra Decision 4) — a provider never registered at startup cannot be hot-enabled at runtime. Hot-reload controls sync behavior of already-registered providers. **Config namespace ownership:** -| Namespace | Scope | Fields | Layer | -| -------------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ---------------- | -| `catalog.providers..*` | Shared infrastructure (startup, TLS, credentials) | `enabled` (startup registration), `tls.caFile`, `credentials.secretRef`, `credentials.secretKey`, `namespace` | YAML-only | -| `boost.connectors..*` | Runtime behavior (hot-reloadable) | `enabled` (sync-skip), `endpoint`, `schedule.intervalMs`, `schedule.cron`, `batchSize`, `timeout.connectionMs` | `db-overridable` | +| Namespace | Scope | Fields | Layer | +| ----------------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ---------------- | +| `ai-catalog.providers..*` | Shared infrastructure (startup, TLS, credentials) | `enabled` (startup registration), `tls.caFile`, `credentials.secretRef`, `credentials.secretKey`, `namespace` | YAML-only | +| `boost.connectors..*` | Runtime behavior (hot-reloadable) | `enabled` (sync-skip), `endpoint`, `schedule.intervalMs`, `schedule.cron`, `batchSize`, `timeout.connectionMs` | `db-overridable` | -- Hot-reload enable/disable, endpoint URL, and schedule changes within 30s +- Hot-reload enable/disable, endpoint URL, and schedule changes — cache refresh ≤30s; takes effect on next reconciliation cycle (worst case ~TTL + schedule interval) - Preserve deployment-time config (TLS mount paths, K8s Secret references) as YAML-only - Admin UI for connector config with RBAC gating - Handle K8s Secret mount propagation delays (up to 60s for projected volumes) @@ -64,7 +64,7 @@ if (!enabled) { const endpoint = (await runtimeConfigResolver.resolve('boost.connectors.jira.endpoint')) || - this.config.getString('catalog.providers.jira.endpoint'); + this.config.getString('ai-catalog.providers.jira.endpoint'); await this.syncClient.connect(endpoint); ``` @@ -72,7 +72,7 @@ await this.syncClient.connect(endpoint); ### Decision 2: configScope annotation strategy -Each `boost.connectors..*` field is `configScope: db-overridable` — these are the runtime-tunable fields. Deployment-time fields (`tls.caFile`, `credentials.*`, `namespace`) live under `catalog.providers..*` and are not part of this schema (see Goals namespace table above). +Each `boost.connectors..*` field is `configScope: db-overridable` — these are the runtime-tunable fields. Deployment-time fields (`tls.caFile`, `credentials.*`, `namespace`) live under `ai-catalog.providers..*` and are not part of this schema (see Goals namespace table above). | Field | configScope | Rationale | | ---------------------- | ---------------- | -------------------------------------------------- | @@ -85,7 +85,7 @@ Each `boost.connectors..*` field is `configScope: db-overridable` — these **Runtime state lives in the health store, not the config resolver:** Fields like `lastSyncTimestamp`, `lastSyncOutcome`, and `runStatus` are pure runtime state owned by the `boost_sync_attempts` table (see ingestion-health-dashboard Decision 1). They are not config — they are operational state written by providers after each sync. Querying them goes through the health API (`GET /api/boost/ingestion-health`), not `RuntimeConfigResolver`. -**Why all fields are db-overridable:** The `boost.connectors` schema only contains runtime-tunable fields by design. Deployment-time fields (mount paths, Secret references, namespace) belong to `catalog.providers` — they can't change at runtime without a pod restart, so they are excluded from this schema entirely rather than marked `yaml-only`. +**Why all fields are db-overridable:** The `boost.connectors` schema only contains runtime-tunable fields by design. Deployment-time fields (mount paths, Secret references, namespace) belong to `ai-catalog.providers` — they can't change at runtime without a pod restart, so they are excluded from this schema entirely rather than marked `yaml-only`. ### Decision 3: Propagation mechanism — polling-based via reconciliation cycles @@ -133,7 +133,9 @@ Admin UI writes connector config changes via `AdminConfigService` — same patte 3. Admin toggles `enabled` or changes `endpoint` 4. Frontend calls `POST /api/boost/admin/config` with flat `BoostConfigKey` — e.g., `{ key: "boost.connectors.jira.enabled", value: false }`. Each write targets a single leaf key; no nested objects, no ambiguity about deep-merge vs replace. 5. Backend validates via Zod schema, writes DB override, calls `RuntimeConfigResolver.invalidate()` (whole-cache invalidation) -6. Frontend shows immediate visual feedback ("Saved — will take effect within 30s + next reconciliation cycle") +6. Frontend shows immediate visual feedback ("Saved — cache refresh ≤30s; will take effect on next reconciliation cycle") + +**Override removal:** To clear a DB override and revert to the YAML baseline value, the admin UI calls `DELETE /api/boost/admin/config?key=`. `AdminConfigService.removeOverride(key)` deletes the DB row and calls `RuntimeConfigResolver.invalidate()`. Use case: switching from `schedule.intervalMs` to `schedule.cron` — the old `intervalMs` override must be removed, not left dangling. If both `schedule.intervalMs` and `schedule.cron` overrides exist simultaneously, `cron` takes precedence. **YAML-only fields (read-only in UI):** TLS mount paths, Secret references shown as read-only info. Tooltip: "Deployment-time config. Edit YAML to change." diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md index 7ff252b8a13..c5d082c5c49 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/proposal.md @@ -14,14 +14,14 @@ The key distinction: Backstage's built-in `ConfigApi` loads config at startup wi ### Config Schemas -- Zod schema definitions for per-connector `boost.connectors.*` fields: `enabled`, `endpoint`, `schedule`, `batchSize`, `timeout` — all `configScope: db-overridable`. Deployment-time fields (`tls`, `credentials`, `namespace`) live under `catalog.providers.*` and are not part of these schemas. +- Zod schema definitions for per-connector `boost.connectors.*` fields: `enabled`, `endpoint`, `schedule`, `batchSize`, `timeout` — all `configScope: db-overridable`. Deployment-time fields (`tls`, `credentials`, `namespace`) live under `ai-catalog.providers.*` and are not part of these schemas. - Runtime operational state (last sync timestamp, run status) lives in the health store (`boost_sync_attempts` table), not the config resolver. - Schema validation rejects invalid connector config values before write - Integration with `RuntimeConfigResolver`'s two-layer resolution ### Hot-Reload Propagation -- Runtime overrides propagate to active entity provider instances within 30s TTL +- Runtime overrides propagate to active entity provider instances — cache refresh ≤30s; takes effect on next reconciliation cycle (worst case ~TTL + schedule interval) - Connector responds to enable/disable changes on next reconciliation cycle - Schedule changes take effect on next reconciliation cycle - Endpoint URL changes take effect on next sync cycle diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md index 50d46df01ff..22042bf5523 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md @@ -22,7 +22,7 @@ Admin UI provides toggle controls for enabling/disabling connectors. - **WHEN** admin clicks toggle to disable Jira connector - **THEN** frontend calls `POST /api/boost/admin/config` with `{ key: "boost.connectors.jira.enabled", value: false }` (flat `BoostConfigKey` — each write targets a single leaf key, no nested objects) - **AND** backend validates via Zod schema, writes DB override, calls `RuntimeConfigResolver.invalidate()` -- **AND** frontend shows immediate visual feedback: "Saved — will take effect within 30 seconds + next reconciliation cycle" +- **AND** frontend shows immediate visual feedback: "Saved — cache refresh ≤30s; will take effect on next reconciliation cycle" - **AND** toggle UI updates to show "Disabled" state #### Scenario: Toggle connector on @@ -72,6 +72,14 @@ Admin UI provides form fields for endpoint URL and sync schedule configuration. - **AND** UI provides cron builder helper (dropdowns for hour, day of week, etc.) - **AND** field shows example: "0 _/2 _ \* \* = every 2 hours" +#### Scenario: Switch from interval to cron removes old override + +- **WHEN** admin switches schedule type from interval to cron and saves +- **THEN** frontend calls `DELETE /api/boost/admin/config?key=boost.connectors.jira.schedule.intervalMs` to remove the old override +- **AND** frontend calls `POST /api/boost/admin/config` with `{ key: "boost.connectors.jira.schedule.cron", value: "0 */2 * * *" }` +- **AND** backend removes intervalMs DB override, writes cron DB override, calls `RuntimeConfigResolver.invalidate()` +- **AND** UI shows: "Saved — schedule switched to cron" + ### Requirement: K8s Secret Reference Field Admin UI displays K8s Secret references as read-only info (deployment-time config). @@ -139,7 +147,7 @@ Admin UI provides immediate validation feedback before and after save. - **WHEN** admin saves valid config change - **THEN** UI displays success notification: "Connector config saved successfully" -- **AND** notification includes propagation info: "Changes will take effect within 30 seconds + next reconciliation cycle" +- **AND** notification includes propagation info: "Changes will take effect after cache refresh (≤30s) + next reconciliation cycle" - **AND** notification auto-dismisses after 5 seconds ### Requirement: RBAC Gating @@ -182,7 +190,7 @@ Admin UI provides immediate visual feedback on save without waiting for propagat #### Scenario: Propagation delay communication - **WHEN** admin saves connector config change -- **THEN** success notification includes: "Saved — will take effect within 30 seconds (cache TTL) + next reconciliation cycle" +- **THEN** success notification includes: "Saved — cache refresh ≤30s; will take effect on next reconciliation cycle" - **AND** UI provides link to connector health dashboard to monitor effect #### Scenario: Config change history diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md index 8ccb2b21fc0..f81b7bedb92 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md @@ -14,7 +14,7 @@ Each connector has a Zod schema defining all configuration fields with `configSc - **WHEN** Jira connector config schema is defined - **THEN** schema includes `boost.connectors` fields only: `enabled` (boolean), `endpoint` (URL string), `schedule.intervalMs` (number), `schedule.cron` (string), `batchSize` (number), `timeout.connectionMs` (number) -- **AND** all fields are `configScope: db-overridable` (deployment-time fields like `tls.caFile`, `credentials.*`, and `namespace` live under `catalog.providers..*` and are not part of this schema) +- **AND** all fields are `configScope: db-overridable` (deployment-time fields like `tls.caFile`, `credentials.*`, and `namespace` live under `ai-catalog.providers..*` and are not part of this schema) #### Scenario: GitHub connector config schema @@ -49,7 +49,7 @@ Each connector has a Zod schema defining all configuration fields with `configSc #### Scenario: Deployment-time field rejects DB override - **WHEN** admin attempts to write DB override for a deployment-time field (e.g., `boost.connectors.jira.credentials.secretRef`) -- **THEN** the write is rejected because `credentials.*`, `tls.*`, and `namespace` are `catalog.providers` fields not present in the `boost.connectors` Zod schema +- **THEN** the write is rejected because `credentials.*`, `tls.*`, and `namespace` are `ai-catalog.providers` fields not present in the `boost.connectors` Zod schema - **AND** admin receives error: "Unknown config key: credentials.secretRef is not a valid boost.connectors field" ### Requirement: Validation Rejection @@ -106,6 +106,23 @@ Connector config schemas define default values for optional fields. - **WHEN** connector config omits `batchSize` - **THEN** schema provides default value (e.g., `100`) +### Requirement: Override Removal + +DB overrides can be removed to revert to the YAML baseline value. + +#### Scenario: Remove override via DELETE endpoint + +- **WHEN** admin calls `DELETE /api/boost/admin/config?key=boost.connectors.jira.schedule.intervalMs` +- **THEN** `AdminConfigService.removeOverride('boost.connectors.jira.schedule.intervalMs')` deletes the DB row +- **AND** `RuntimeConfigResolver.invalidate()` is called +- **AND** next `resolve('boost.connectors.jira.schedule.intervalMs')` returns the YAML baseline value (or schema default if no YAML value) + +#### Scenario: Schedule type precedence when both overrides exist + +- **WHEN** DB overrides exist for both `boost.connectors.jira.schedule.intervalMs` and `boost.connectors.jira.schedule.cron` +- **THEN** `schedule.cron` takes precedence — provider uses cron-based scheduling +- **AND** provider logs warning: "Both schedule.intervalMs and schedule.cron are set; using cron" + ## ADDED Requirements ### Requirement: Specification Coverage diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/hot-reload-propagation/spec.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/hot-reload-propagation/spec.md index a459a6186c4..cfb450dabae 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/hot-reload-propagation/spec.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/hot-reload-propagation/spec.md @@ -2,13 +2,13 @@ > **Status: Draft** — Pre-implementation specification. Subject to change during implementation. -Runtime overrides propagate to active connector instances within 30s TTL. Connector responds to config changes on next reconciliation cycle. Endpoint URL changes, schedule changes, and enable/disable toggles take effect without pod restart. +Runtime overrides propagate to active connector instances — cache refresh ≤30s; takes effect on next reconciliation cycle (worst case ~TTL + schedule interval). Connector responds to config changes on next reconciliation cycle. Endpoint URL changes, schedule changes, and enable/disable toggles take effect without pod restart. ## EXISTING Requirements ### Requirement: Enable/Disable Propagation -Enable/disable changes propagate to active entity provider within 30s + reconciliation interval. +Enable/disable changes propagate to active entity provider — cache refresh ≤30s; takes effect on next reconciliation cycle. #### Scenario: Disable connector via DB override @@ -53,11 +53,18 @@ Schedule changes take effect on next reconciliation cycle. #### Scenario: Switch from interval to cron schedule -- **WHEN** admin writes DB override `boost.connectors.jira.schedule.cron: "0 */2 * * *"` (every 2 hours) and removes `schedule.intervalMs` -- **THEN** next reconciliation cycle reads new config +- **WHEN** admin writes DB override `boost.connectors.jira.schedule.cron: "0 */2 * * *"` (every 2 hours) and removes the `schedule.intervalMs` override via `DELETE /api/boost/admin/config?key=boost.connectors.jira.schedule.intervalMs` +- **THEN** `AdminConfigService.removeOverride('boost.connectors.jira.schedule.intervalMs')` deletes the DB row, then calls `RuntimeConfigResolver.invalidate()` +- **AND** next reconciliation cycle reads new config - **AND** provider switches from interval-based to cron-based scheduling - **AND** subsequent cycles run at cron-specified times +#### Scenario: Precedence when both intervalMs and cron overrides exist + +- **WHEN** DB overrides exist for both `schedule.intervalMs` and `schedule.cron` +- **THEN** `schedule.cron` takes precedence — cron-based scheduling is used +- **AND** provider logs warning: "Both schedule.intervalMs and schedule.cron are set; using cron" + ### Requirement: Endpoint URL Change Propagation Endpoint URL changes take effect on next sync cycle. diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md index e3196967671..4ebdac19bac 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/tasks.md @@ -2,8 +2,8 @@ ## 1. Zod Schema Definitions (P0) — RHIDP-15340 -- [ ] 1.1 Define Jira connector config Zod schema with `boost.connectors` fields only: `enabled` (boolean), `endpoint` (URL), `schedule.intervalMs` (number), `schedule.cron` (string), `batchSize` (number), `timeout.connectionMs` (number). Note: `tls.caFile`, `credentials.*`, and `namespace` are `catalog.providers` fields — not part of the `boost.connectors` schema. -- [ ] 1.2 All `boost.connectors` fields are `configScope: db-overridable` (deployment-time fields like `credentials.*`, `tls.*`, and `namespace` live under `catalog.providers..*`) +- [ ] 1.1 Define Jira connector config Zod schema with `boost.connectors` fields only: `enabled` (boolean), `endpoint` (URL), `schedule.intervalMs` (number), `schedule.cron` (string), `batchSize` (number), `timeout.connectionMs` (number). Note: `tls.caFile`, `credentials.*`, and `namespace` are `ai-catalog.providers` fields — not part of the `boost.connectors` schema. +- [ ] 1.2 All `boost.connectors` fields are `configScope: db-overridable` (deployment-time fields like `credentials.*`, `tls.*`, and `namespace` live under `ai-catalog.providers..*`) - [ ] 1.3 Define GitHub connector config Zod schema with connector-appropriate field subset (`enabled`, `endpoint`, `schedule.intervalMs`, `batchSize`) - [ ] 1.4 Define GitLab connector config Zod schema with connector-appropriate field subset (`enabled`, `endpoint`, `schedule.intervalMs`, `batchSize`) - [ ] 1.5 Add URL validation for `endpoint` field (must be valid https:// URL) @@ -49,7 +49,7 @@ - [ ] 4.4 Display K8s Secret references as read-only fields with tooltip: "Deployment-time config. Edit YAML to change." - [ ] 4.5 Implement client-side validation: URL format for `endpoint`, positive numbers for `schedule.intervalMs`, cron syntax for `schedule.cron` - [ ] 4.6 Implement save handler: call `POST /api/boost/admin/config` with connector key and updated fields -- [ ] 4.7 Implement success notification: "Saved — will take effect within 30 seconds + next reconciliation cycle" +- [ ] 4.7 Implement success notification: "Saved — cache refresh ≤30s; will take effect on next reconciliation cycle" - [ ] 4.8 Implement validation error feedback: display server-side Zod validation errors inline - [ ] 4.9 Implement RBAC gating: require `ai-catalog.admin` permission to access connector config section (via `permissions.authorize()`) - [ ] 4.10 Implement read-only view for non-admin users (if configured) @@ -69,8 +69,9 @@ - [ ] 6.2 Implement Zod schema validation in `setOverride()` method before DB write - [ ] 6.3 Implement `configScope` enforcement: reject writes for `yaml-only` fields - [ ] 6.4 Implement cache invalidation call to `RuntimeConfigResolver.invalidate()` after DB write -- [ ] 6.5 Add audit logging for connector config changes (timestamp, user, changed fields, old/new values) -- [ ] 6.6 Add unit tests for `AdminConfigService` connector config methods +- [ ] 6.5 Implement `removeOverride(key: BoostConfigKey)` method and `DELETE /api/boost/admin/config?key=` endpoint — deletes the DB override row, calls `RuntimeConfigResolver.invalidate()`, and returns the reverted YAML baseline value. Used when switching schedule types (e.g., removing `intervalMs` override when switching to `cron`). +- [ ] 6.6 Add audit logging for connector config changes (timestamp, user, changed fields, old/new values) +- [ ] 6.7 Add unit tests for `AdminConfigService` connector config methods (including `removeOverride`) ## 7. Testing (P1) diff --git a/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/admin-health-ui/spec.md b/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/admin-health-ui/spec.md index 81a64b27c6c..5fd7d2d8eba 100644 --- a/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/admin-health-ui/spec.md +++ b/workspaces/boost/openspec/changes/ingestion-health-dashboard/specs/admin-health-ui/spec.md @@ -89,7 +89,7 @@ Disabled connectors visually distinct from failing ones. - **THEN** the health card renders with grey/muted status badge (`variant="outline"` PatternFly Label) showing "Disabled" - **AND** no red/error indicators are shown - **AND** the card body shows "Connector is disabled. Enable in connector config to resume sync." message -- **AND** note: connectors with `catalog.providers.*.enabled: false` (startup registration gate) are never registered and absent from the UI entirely +- **AND** note: connectors with `ai-catalog.providers.*.enabled: false` (startup registration gate) are never registered and absent from the UI entirely #### Scenario: Failing connector uses alert treatment From f0fea0e9e8edf9a4722d98a37638bbd53566bbe2 Mon Sep 17 00:00:00 2001 From: gabemontero Date: Mon, 27 Jul 2026 15:58:04 -0400 Subject: [PATCH 19/19] =?UTF-8?q?fix(boost):=20RHDHPLAN-1513=20address=20f?= =?UTF-8?q?ullsend=20review=20=E2=80=94=20pre-existing=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Confluence → GitLab typo in ingestion-audit-metrics/tasks.md 2. runStatus clarified as derived, not a stored column (design.md) 3. TIMESTAMP → TIMESTAMPTZ in boost_quality_scores schema 4. __schemaVersion Zod/boostConfigFields interaction clarified 5. Cron example backtick-wrapped to prevent markdown mangling All pre-existing issues unrelated to the Marek review fixes in a1edca3c07. Co-Authored-By: Claude Opus 4.6 Signed-off-by: gabemontero --- .../openspec/changes/connector-config-hot-reload/design.md | 2 +- .../connector-config-hot-reload/specs/config-admin-ui/spec.md | 2 +- .../connector-config-hot-reload/specs/config-schemas/spec.md | 2 +- .../boost/openspec/changes/ingestion-audit-metrics/design.md | 2 +- .../boost/openspec/changes/ingestion-audit-metrics/tasks.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md index ac6c0111393..7f362484c86 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/design.md @@ -83,7 +83,7 @@ Each `boost.connectors..*` field is `configScope: db-overridable` — these | `batchSize` | `db-overridable` | Admin can tune performance at runtime | | `timeout.connectionMs` | `db-overridable` | Admin can adjust for network conditions at runtime | -**Runtime state lives in the health store, not the config resolver:** Fields like `lastSyncTimestamp`, `lastSyncOutcome`, and `runStatus` are pure runtime state owned by the `boost_sync_attempts` table (see ingestion-health-dashboard Decision 1). They are not config — they are operational state written by providers after each sync. Querying them goes through the health API (`GET /api/boost/ingestion-health`), not `RuntimeConfigResolver`. +**Runtime state lives in the health store, not the config resolver:** Fields like `lastSyncTimestamp` and `lastSyncOutcome` are pure runtime state owned by the `boost_sync_attempts` table (see ingestion-health-dashboard Decision 1). They are not config — they are operational state written by providers after each sync. Run status (running/idle) is derived from these fields, not stored as a separate column. Querying them goes through the health API (`GET /api/boost/ingestion-health`), not `RuntimeConfigResolver`. **Why all fields are db-overridable:** The `boost.connectors` schema only contains runtime-tunable fields by design. Deployment-time fields (mount paths, Secret references, namespace) belong to `ai-catalog.providers` — they can't change at runtime without a pod restart, so they are excluded from this schema entirely rather than marked `yaml-only`. diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md index 22042bf5523..5082198b8dd 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-admin-ui/spec.md @@ -70,7 +70,7 @@ Admin UI provides form fields for endpoint URL and sync schedule configuration. - **WHEN** admin switches from interval to cron schedule - **THEN** UI displays cron expression input field with validation - **AND** UI provides cron builder helper (dropdowns for hour, day of week, etc.) -- **AND** field shows example: "0 _/2 _ \* \* = every 2 hours" +- **AND** field shows example: "`0 */2 * * *` = every 2 hours" #### Scenario: Switch from interval to cron removes old override diff --git a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md index f81b7bedb92..d0a3bf03f0b 100644 --- a/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md +++ b/workspaces/boost/openspec/changes/connector-config-hot-reload/specs/config-schemas/spec.md @@ -82,7 +82,7 @@ Connector config schemas support versioning for backward compatibility. - **WHEN** admin writes DB override for a leaf key under `boost.connectors.jira` (e.g., `boost.connectors.jira.enabled`) - **THEN** the DB entry stores the leaf key and value (each write targets a single `BoostConfigKey`); concurrent writes to different leaves under the same connector do not conflict -- **AND** the connector-level schema version is stored as an explicit leaf key `boost.connectors.jira.__schemaVersion` (e.g., value `1`), bumped only during schema migrations — not on individual config writes +- **AND** the connector-level schema version is stored as an explicit leaf key `boost.connectors.jira.__schemaVersion` (e.g., value `1`), bumped only during schema migrations — not on individual config writes. `__schemaVersion` is a metadata key excluded from per-leaf Zod validation; it must be registered in `boostConfigFields` with `configScope: 'db-only'` to survive the `validateStoredValues()` startup sweep - **AND** `GET /api/boost/admin/config?key=boost.connectors.jira` returns all leaf overrides matching that prefix, merged into a single object for the response - **AND** Zod schema validation is applied at the connector level (aggregating all leaf values) on GET-prefix queries to ensure cross-field consistency diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md index 76d039b88b3..bcbbd6ae9ac 100644 --- a/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/design.md @@ -130,7 +130,7 @@ CREATE TABLE boost_quality_scores ( skill_entity_ref VARCHAR(255) NOT NULL, eval_source VARCHAR(100) NOT NULL, -- 'lighteval', 'ibm-clear', 'guidellm', etc. score DECIMAL(5, 3) NOT NULL CHECK (score >= 0.0 AND score <= 1.0), -- 0.000 to 1.000 (normalized) - timestamp TIMESTAMP NOT NULL DEFAULT NOW(), + timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(), metadata JSONB -- extensible for eval-framework-specific data ); diff --git a/workspaces/boost/openspec/changes/ingestion-audit-metrics/tasks.md b/workspaces/boost/openspec/changes/ingestion-audit-metrics/tasks.md index 17934ea5bda..1daea8f3a80 100644 --- a/workspaces/boost/openspec/changes/ingestion-audit-metrics/tasks.md +++ b/workspaces/boost/openspec/changes/ingestion-audit-metrics/tasks.md @@ -21,7 +21,7 @@ - [ ] 2.4 Emit `ingestion.sync.success` event in `onSyncSuccess` hook with asset counts - [ ] 2.5 Add `onSyncFailure` lifecycle hook with error parameter - [ ] 2.6 Emit `ingestion.sync.failure` event in `onSyncFailure` hook with error details -- [ ] 2.7 Integrate audit event emission into existing connectors (GitHub, Jira, Confluence) +- [ ] 2.7 Integrate audit event emission into existing connectors (GitHub, GitLab, Jira) - [ ] 2.8 Add asset count tracking logic (added/updated/removed entities) - [ ] 2.9 Add error serialization for sync failure events - [ ] 2.10 Add integration tests for sync audit events end-to-end