Spaces:
Sleeping
Sleeping
harryagasi
[NOTICKET] feat(knowledge,analysis): add deletion confirmation, duplicate-name guard, and unusable-analysis lockout
3baad7e | # Data Model: Knowledge Source Deletion Guardrails | |
| No new persisted entities or backend schema changes. This feature only adds client-side derived values computed from existing `orchestrationApi` types (`src/services/orchestrationApi.ts`). | |
| ## Existing entities reused (unchanged) | |
| - **`ApiDocument`** (`orchestrationApi.ts:60`-ish): `{ id, filename, file_type, file_size, status, created_at, ... }` β a document knowledge source. | |
| - **`DatabaseClient`**: `{ id, name, db_type, status, ... }` β a database knowledge source. | |
| - **`DataBindItem`** (`orchestrationApi.ts:150`): `{ id, name, group_type: "document" | "database", type }` β a reference from an `Analysis` to a bound knowledge source. | |
| - **`Analysis`** (`orchestrationApi.ts:157`): `{ id, analysis_title, objective, business_questions, status: "active" | "inactive" | string, data_bind: DataBindItem[], data_bind_version, ... }`. `status` is **not** modified by this feature (see Clarifications in spec.md). | |
| ## New client-side derived values (not persisted, not sent to the backend) | |
| ### `DeletionImpactSummary` | |
| Computed at delete-intent time in `KnowledgeManagement.tsx`, before the confirmation dialog opens. | |
| | Field | Type | Description | | |
| |---|---|---| | |
| | `sourceIds` | `string[]` | The knowledge source id(s) about to be deleted (one for single delete, many for "clear all"). | | |
| | `affectedAnalyses` | `{ id: string; analysis_title: string }[]` | Analyses whose `data_bind` contains at least one of `sourceIds`, deduplicated by analysis id. Derived by fetching `listAnalyses()` and filtering `analysis.data_bind.some(item => sourceIds.includes(item.id))`. | | |
| **Lifecycle**: Computed fresh each time a delete action is initiated; discarded when the dialog closes (confirm or cancel). Never persisted. | |
| ### Analysis usability flag (`isUnusable`) | |
| **Reused, not newly computed**: `AnalysisShell.tsx` already maintains `staleSources: DataBindItem[]` (state at `AnalysisShell.tsx:80`, computed in the `useEffect` at `AnalysisShell.tsx:117-138`) by cross-referencing `activeAnalysis.data_bind` against live `getDocuments`/`getDatabaseClients` results β this is exactly the "unusable" derivation this feature needs, built for feature 002's stale-sources banner. This feature derives from it rather than re-implementing it: | |
| ``` | |
| isActiveAnalysisUnusable: boolean = staleSources.length > 0 | |
| ``` | |
| - Any single missing bound source is sufficient to mark the whole analysis unusable (per spec Assumptions / Acceptance Scenario US3.6) β already true of `staleSources`'s existing filter logic. | |
| - This is a pure, stateless derivation β not stored on the `Analysis` object returned by the API, and never written back via `updateAnalysis`/`UpdateAnalysisPayload`. | |
| - Recomputed on-demand (page load / analysis selection), not via a live subscription (per research.md Unknown 3 and the plan's Constraints) β matching `staleSources`'s existing recompute trigger (the `activeAnalysis`-keyed effect). | |
| **State transitions**: `isActiveAnalysisUnusable` has no explicit transition function in this feature β it is a pure function of current data. It can only become `true` when a bound source is deleted (User Story 1 β 3). Per clarification, this feature has **no** transition back to `false`: once `staleSources.length > 0`, the update-bound-data action (including the pre-existing "Update binding" control that would normally clear `staleSources`) is itself disabled by this feature (FR-011/FR-014), so there is no in-app path to restore usability. | |
| ## Validation rule: analysis name uniqueness | |
| Enforced client-side in `NewAnalysisDialog.tsx` before calling `createAnalysis()`: | |
| ``` | |
| isDuplicateName(candidate: string, existing: Analysis[]): boolean = | |
| existing.some(a => a.analysis_title.trim().toLowerCase() === candidate.trim().toLowerCase()) | |
| ``` | |
| - Requires the dialog to have access to the current analysis list (already loaded by the parent `AnalysisShell.tsx` as `analyses` state) β passed down as a new prop, or fetched via `listAnalyses()` if the dialog is opened without it in scope. | |
| - This is a client-side guard only; per FR-007/FR-008 no backend contract change is assumed to exist, so this check does not guarantee uniqueness under concurrent creation from two sessions (acceptable per spec β no server-side uniqueness constraint was requested). | |